888 lines
29 KiB
Go
888 lines
29 KiB
Go
package geth_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math/big"
|
|
"reflect"
|
|
"testing"
|
|
"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/status-im/status-go/geth"
|
|
"github.com/status-im/status-go/geth/params"
|
|
)
|
|
|
|
func TestQueuedContracts(t *testing.T) {
|
|
err := geth.PrepareTestNode()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// obtain reference to status backend
|
|
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
|
if err != nil {
|
|
t.Errorf("Test failed: LES service is not running: %v", err)
|
|
return
|
|
}
|
|
backend := lightEthereum.StatusBackend
|
|
|
|
// create an account
|
|
sampleAddress, _, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("could not create account: %v", err)
|
|
return
|
|
}
|
|
|
|
if err = geth.Logout(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
completeQueuedTransaction := make(chan struct{}, 1)
|
|
geth.PanicAfter(5*time.Second, completeQueuedTransaction, "TestQueuedContracts")
|
|
|
|
// replace transaction notification handler
|
|
var txHash = common.Hash{}
|
|
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint :dupl
|
|
var envelope geth.SignalEnvelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == geth.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
t.Logf("transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
|
|
|
// the first call will fail (we are not logged in, but trying to complete tx)
|
|
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
|
|
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
|
|
return
|
|
}
|
|
|
|
// the second call will also fail (we are logged in as different user)
|
|
if err := geth.SelectAccount(sampleAddress, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", sampleAddress)
|
|
return
|
|
}
|
|
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
|
|
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
|
|
return
|
|
}
|
|
|
|
// the third call will work as expected (as we are logged in with correct credentials)
|
|
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
|
return
|
|
}
|
|
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
|
|
return
|
|
}
|
|
|
|
t.Logf("contract transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
|
close(completeQueuedTransaction)
|
|
}
|
|
})
|
|
|
|
// this call blocks, up until Complete Transaction is called
|
|
byteCode, err := hexutil.Decode(`0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029`)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
|
From: geth.FromAddress(testConfig.Account1.Address),
|
|
To: nil, // marker, contract creation is expected
|
|
//Value: (*hexutil.Big)(new(big.Int).Mul(big.NewInt(1), common.Ether)),
|
|
Gas: (*hexutil.Big)(big.NewInt(params.DefaultGas)),
|
|
Data: byteCode,
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Test failed: cannot send transaction: %v", err)
|
|
}
|
|
|
|
if txHashCheck.Hex() != txHash.Hex() {
|
|
t.Errorf("Transaction hash returned from SendTransaction is invalid: expected %s, got %s", txHashCheck.Hex(), txHash.Hex())
|
|
return
|
|
}
|
|
|
|
<-completeQueuedTransaction
|
|
|
|
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
|
t.Error("Test failed: transaction was never queued or completed")
|
|
return
|
|
}
|
|
|
|
if backend.TransactionQueue().Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestQueuedTransactions(t *testing.T) {
|
|
err := geth.PrepareTestNode()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// obtain reference to status backend
|
|
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
|
if err != nil {
|
|
t.Errorf("Test failed: LES service is not running: %v", err)
|
|
return
|
|
}
|
|
backend := lightEthereum.StatusBackend
|
|
|
|
// create an account
|
|
sampleAddress, _, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("could not create account: %v", err)
|
|
return
|
|
}
|
|
|
|
if err = geth.Logout(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
completeQueuedTransaction := make(chan struct{}, 1)
|
|
geth.PanicAfter(5*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
|
|
|
|
// replace transaction notification handler
|
|
var txHash = common.Hash{}
|
|
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint: dupl
|
|
var envelope geth.SignalEnvelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == geth.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
t.Logf("transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
|
|
|
// the first call will fail (we are not logged in, but trying to complete tx)
|
|
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
|
|
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
|
|
return
|
|
}
|
|
|
|
// the second call will also fail (we are logged in as different user)
|
|
if err := geth.SelectAccount(sampleAddress, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", sampleAddress)
|
|
return
|
|
}
|
|
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
|
|
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
|
|
return
|
|
}
|
|
|
|
// the third call will work as expected (as we are logged in with correct credentials)
|
|
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
|
return
|
|
}
|
|
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
|
|
return
|
|
}
|
|
|
|
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
|
close(completeQueuedTransaction)
|
|
}
|
|
})
|
|
|
|
// this call blocks, up until Complete Transaction is called
|
|
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
|
From: geth.FromAddress(testConfig.Account1.Address),
|
|
To: geth.ToAddress(testConfig.Account2.Address),
|
|
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Test failed: cannot send transaction: %v", err)
|
|
}
|
|
|
|
if txHashCheck.Hex() != txHash.Hex() {
|
|
t.Errorf("Transaction hash returned from SendTransaction is invalid: expected %s, got %s", txHashCheck.Hex(), txHash.Hex())
|
|
return
|
|
}
|
|
|
|
<-completeQueuedTransaction
|
|
|
|
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
|
t.Error("Test failed: transaction was never queued or completed")
|
|
return
|
|
}
|
|
|
|
if backend.TransactionQueue().Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestDoubleCompleteQueuedTransactions(t *testing.T) {
|
|
err := geth.PrepareTestNode()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// obtain reference to status backend
|
|
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
|
if err != nil {
|
|
t.Errorf("Test failed: LES service is not running: %v", err)
|
|
return
|
|
}
|
|
backend := lightEthereum.StatusBackend
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
|
return
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
completeQueuedTransaction := make(chan struct{}, 1)
|
|
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
|
|
|
|
// replace transaction notification handler
|
|
var txID string
|
|
txFailedEventCalled := false
|
|
txHash := common.Hash{}
|
|
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var envelope geth.SignalEnvelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == geth.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
txID = event["id"].(string)
|
|
t.Logf("transaction queued (will be failed and completed on the second call): {id: %s}\n", txID)
|
|
|
|
// try with wrong password
|
|
// make sure that tx is NOT removed from the queue (by re-trying with the correct password)
|
|
if _, err = geth.CompleteTransaction(txID, testConfig.Account1.Password+"wrong"); err != keystore.ErrDecrypt {
|
|
t.Errorf("expects wrong password error, but call succeeded (or got another error: %v)", err)
|
|
return
|
|
}
|
|
|
|
if txCount := backend.TransactionQueue().Count(); txCount != 1 {
|
|
t.Errorf("txqueue cannot be empty, as tx has failed: expected = 1, got = %d", txCount)
|
|
return
|
|
}
|
|
|
|
// now try to complete transaction, but with the correct password
|
|
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
|
|
return
|
|
}
|
|
|
|
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
|
if txCount := backend.TransactionQueue().Count(); txCount != 0 {
|
|
t.Errorf("txqueue must be empty, as tx has completed: expected = 0, got = %d", txCount)
|
|
return
|
|
}
|
|
|
|
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
|
completeQueuedTransaction <- struct{}{} // so that timeout is aborted
|
|
}
|
|
|
|
if envelope.Type == geth.EventTransactionFailed {
|
|
event := envelope.Event.(map[string]interface{})
|
|
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
|
|
|
receivedErrMessage := event["error_message"].(string)
|
|
expectedErrMessage := "could not decrypt key with given passphrase"
|
|
if receivedErrMessage != expectedErrMessage {
|
|
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
|
return
|
|
}
|
|
|
|
receivedErrCode := event["error_code"].(string)
|
|
if receivedErrCode != geth.SendTransactionPasswordErrorCode {
|
|
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
|
return
|
|
}
|
|
|
|
txFailedEventCalled = true
|
|
}
|
|
})
|
|
|
|
// this call blocks, and should return on *second* attempt to CompleteTransaction (w/ the correct password)
|
|
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
|
From: geth.FromAddress(testConfig.Account1.Address),
|
|
To: geth.ToAddress(testConfig.Account2.Address),
|
|
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
})
|
|
if err != nil {
|
|
t.Errorf("cannot send transaction: %v", err)
|
|
return
|
|
}
|
|
|
|
if txHashCheck.Hex() != txHash.Hex() {
|
|
t.Errorf("tx hash returned from SendTransaction is invalid: expected %s, got %s", txHashCheck.Hex(), txHash.Hex())
|
|
return
|
|
}
|
|
|
|
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
|
t.Error("transaction was never queued or completed")
|
|
return
|
|
}
|
|
|
|
if backend.TransactionQueue().Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return
|
|
}
|
|
|
|
if !txFailedEventCalled {
|
|
t.Error("expected tx failure signal is not received")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestDiscardQueuedTransactions(t *testing.T) {
|
|
err := geth.PrepareTestNode()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// obtain reference to status backend
|
|
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
|
if err != nil {
|
|
t.Errorf("Test failed: LES service is not running: %v", err)
|
|
return
|
|
}
|
|
backend := lightEthereum.StatusBackend
|
|
|
|
// reset queue
|
|
backend.TransactionQueue().Reset()
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
|
return
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
completeQueuedTransaction := make(chan struct{}, 1)
|
|
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestDiscardQueuedTransactions")
|
|
|
|
// replace transaction notification handler
|
|
var txID string
|
|
txFailedEventCalled := false
|
|
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var envelope geth.SignalEnvelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == geth.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
txID = event["id"].(string)
|
|
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
|
|
|
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
|
|
t.Errorf("txqueue should still have test tx: %s", txID)
|
|
return
|
|
}
|
|
|
|
// discard
|
|
err := geth.DiscardTransaction(txID)
|
|
if err != nil {
|
|
t.Errorf("cannot discard tx: %v", err)
|
|
return
|
|
}
|
|
|
|
// try completing discarded transaction
|
|
_, err = geth.CompleteTransaction(txID, testConfig.Account1.Password)
|
|
if err.Error() != "transaction hash not found" {
|
|
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)) {
|
|
t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
|
|
return
|
|
}
|
|
|
|
completeQueuedTransaction <- struct{}{} // so that timeout is aborted
|
|
}
|
|
|
|
if envelope.Type == geth.EventTransactionFailed {
|
|
event := envelope.Event.(map[string]interface{})
|
|
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
|
|
|
receivedErrMessage := event["error_message"].(string)
|
|
expectedErrMessage := status.ErrQueuedTxDiscarded.Error()
|
|
if receivedErrMessage != expectedErrMessage {
|
|
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
|
return
|
|
}
|
|
|
|
receivedErrCode := event["error_code"].(string)
|
|
if receivedErrCode != geth.SendTransactionDiscardedErrorCode {
|
|
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
|
return
|
|
}
|
|
|
|
txFailedEventCalled = true
|
|
}
|
|
})
|
|
|
|
// this call blocks, and should return when DiscardQueuedTransaction() is called
|
|
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
|
From: geth.FromAddress(testConfig.Account1.Address),
|
|
To: geth.ToAddress(testConfig.Account2.Address),
|
|
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
})
|
|
if err != status.ErrQueuedTxDiscarded {
|
|
t.Errorf("expected error not thrown: %v", err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
|
t.Error("transaction returned hash, while it shouldn't")
|
|
return
|
|
}
|
|
|
|
if backend.TransactionQueue().Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return
|
|
}
|
|
|
|
if !txFailedEventCalled {
|
|
t.Error("expected tx failure signal is not received")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestCompleteMultipleQueuedTransactions(t *testing.T) {
|
|
err := geth.PrepareTestNode()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// obtain reference to status backend
|
|
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
|
if err != nil {
|
|
t.Errorf("Test failed: LES service is not running: %v", err)
|
|
return
|
|
}
|
|
backend := lightEthereum.StatusBackend
|
|
|
|
// reset queue
|
|
backend.TransactionQueue().Reset()
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
|
return
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
testTxCount := 3
|
|
txIDs := make(chan string, testTxCount)
|
|
allTestTxCompleted := make(chan struct{}, 1)
|
|
|
|
// replace transaction notification handler
|
|
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var txID string
|
|
var envelope geth.SignalEnvelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == geth.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
txID = event["id"].(string)
|
|
t.Logf("transaction queued (will be completed in a single call, once aggregated): {id: %s}\n", txID)
|
|
|
|
txIDs <- txID
|
|
}
|
|
})
|
|
|
|
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
|
sendTx := func() {
|
|
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
|
From: geth.FromAddress(testConfig.Account1.Address),
|
|
To: geth.ToAddress(testConfig.Account2.Address),
|
|
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
})
|
|
if err != nil {
|
|
t.Errorf("unexpected error thrown: %v", err)
|
|
return
|
|
}
|
|
|
|
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
|
t.Error("transaction returned empty hash")
|
|
return
|
|
}
|
|
}
|
|
|
|
// wait for transactions, and complete them in a single call
|
|
completeTxs := func(txIDStrings string) {
|
|
var parsedIDs []string
|
|
if err := json.Unmarshal([]byte(txIDStrings), &parsedIDs); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
parsedIDs = append(parsedIDs, "invalid-tx-id")
|
|
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
|
|
|
// complete
|
|
results := geth.CompleteTransactions(string(updatedTxIDStrings), testConfig.Account1.Password)
|
|
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error.Error() != "transaction hash not found" {
|
|
t.Errorf("cannot complete txs: %v", results)
|
|
return
|
|
}
|
|
for txID, txResult := range results {
|
|
if txResult.Error != nil && txID != "invalid-tx-id" {
|
|
t.Errorf("invalid error for %s", txID)
|
|
return
|
|
}
|
|
if txResult.Hash.Hex() == "0x0000000000000000000000000000000000000000000000000000000000000000" && txID != "invalid-tx-id" {
|
|
t.Errorf("invalid hash (expected non empty hash): %s", txID)
|
|
return
|
|
}
|
|
|
|
if txResult.Hash.Hex() != "0x0000000000000000000000000000000000000000000000000000000000000000" {
|
|
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txResult.Hash.Hex())
|
|
}
|
|
}
|
|
|
|
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
|
for _, txID := range parsedIDs {
|
|
if backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
|
|
t.Errorf("txqueue should not have test tx at this point (it should be completed): %s", txID)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
go func() {
|
|
var txIDStrings []string
|
|
for i := 0; i < testTxCount; i++ {
|
|
txIDStrings = append(txIDStrings, <-txIDs)
|
|
}
|
|
|
|
txIDJSON, _ := json.Marshal(txIDStrings)
|
|
completeTxs(string(txIDJSON))
|
|
allTestTxCompleted <- struct{}{}
|
|
}()
|
|
|
|
// send multiple transactions
|
|
for i := 0; i < testTxCount; i++ {
|
|
go sendTx()
|
|
}
|
|
|
|
select {
|
|
case <-allTestTxCompleted:
|
|
// pass
|
|
case <-time.After(20 * time.Second):
|
|
t.Error("test timed out")
|
|
return
|
|
}
|
|
|
|
if backend.TransactionQueue().Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestDiscardMultipleQueuedTransactions(t *testing.T) {
|
|
err := geth.PrepareTestNode()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// obtain reference to status backend
|
|
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
|
if err != nil {
|
|
t.Errorf("Test failed: LES service is not running: %v", err)
|
|
return
|
|
}
|
|
backend := lightEthereum.StatusBackend
|
|
|
|
// reset queue
|
|
backend.TransactionQueue().Reset()
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
|
return
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
testTxCount := 3
|
|
txIDs := make(chan string, testTxCount)
|
|
allTestTxDiscarded := make(chan struct{}, 1)
|
|
|
|
// replace transaction notification handler
|
|
txFailedEventCallCount := 0
|
|
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var txID string
|
|
var envelope geth.SignalEnvelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == geth.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
txID = event["id"].(string)
|
|
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
|
|
|
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
|
|
t.Errorf("txqueue should still have test tx: %s", txID)
|
|
return
|
|
}
|
|
|
|
txIDs <- txID
|
|
}
|
|
|
|
if envelope.Type == geth.EventTransactionFailed {
|
|
event := envelope.Event.(map[string]interface{})
|
|
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
|
|
|
receivedErrMessage := event["error_message"].(string)
|
|
expectedErrMessage := status.ErrQueuedTxDiscarded.Error()
|
|
if receivedErrMessage != expectedErrMessage {
|
|
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
|
return
|
|
}
|
|
|
|
receivedErrCode := event["error_code"].(string)
|
|
if receivedErrCode != geth.SendTransactionDiscardedErrorCode {
|
|
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
|
return
|
|
}
|
|
|
|
txFailedEventCallCount++
|
|
if txFailedEventCallCount == testTxCount {
|
|
allTestTxDiscarded <- struct{}{}
|
|
}
|
|
}
|
|
})
|
|
|
|
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
|
sendTx := func() {
|
|
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
|
From: geth.FromAddress(testConfig.Account1.Address),
|
|
To: geth.ToAddress(testConfig.Account2.Address),
|
|
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
})
|
|
if err != status.ErrQueuedTxDiscarded {
|
|
t.Errorf("expected error not thrown: %v", err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
|
t.Error("transaction returned hash, while it shouldn't")
|
|
return
|
|
}
|
|
}
|
|
|
|
// wait for transactions, and discard immediately
|
|
discardTxs := func(txIDStrings string) {
|
|
var parsedIDs []string
|
|
if err := json.Unmarshal([]byte(txIDStrings), &parsedIDs); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
parsedIDs = append(parsedIDs, "invalid-tx-id")
|
|
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
|
|
|
// discard
|
|
discardResults := geth.DiscardTransactions(string(updatedTxIDStrings))
|
|
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error.Error() != "transaction hash not found" {
|
|
t.Errorf("cannot discard txs: %v", discardResults)
|
|
return
|
|
}
|
|
|
|
// try completing discarded transaction
|
|
completeResults := geth.CompleteTransactions(string(updatedTxIDStrings), testConfig.Account1.Password)
|
|
if len(completeResults) != (testTxCount + 1) {
|
|
t.Error("unexpected number of errors (call to CompleteTransaction should not succeed)")
|
|
}
|
|
for _, txResult := range completeResults {
|
|
if txResult.Error.Error() != "transaction hash not found" {
|
|
t.Errorf("invalid error for %s", txResult.Hash.Hex())
|
|
return
|
|
}
|
|
if txResult.Hash.Hex() != "0x0000000000000000000000000000000000000000000000000000000000000000" {
|
|
t.Errorf("invalid hash (expected zero): %s", txResult.Hash.Hex())
|
|
return
|
|
}
|
|
}
|
|
|
|
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
|
for _, txID := range parsedIDs {
|
|
if backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
|
|
t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
go func() {
|
|
var txIDStrings []string
|
|
for i := 0; i < testTxCount; i++ {
|
|
txIDStrings = append(txIDStrings, <-txIDs)
|
|
}
|
|
|
|
txIDJSON, _ := json.Marshal(txIDStrings)
|
|
discardTxs(string(txIDJSON))
|
|
}()
|
|
|
|
// send multiple transactions
|
|
for i := 0; i < testTxCount; i++ {
|
|
go sendTx()
|
|
}
|
|
|
|
select {
|
|
case <-allTestTxDiscarded:
|
|
// pass
|
|
case <-time.After(20 * time.Second):
|
|
t.Error("test timed out")
|
|
return
|
|
}
|
|
|
|
if backend.TransactionQueue().Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestNonExistentQueuedTransactions(t *testing.T) {
|
|
err := geth.PrepareTestNode()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
|
return
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
completeQueuedTransaction := make(chan struct{}, 1)
|
|
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
|
|
|
|
// replace transaction notification handler
|
|
geth.SetDefaultNodeNotificationHandler(func(string) {})
|
|
|
|
// try completing non-existing transaction
|
|
if _, err = geth.CompleteTransaction("some-bad-transaction-id", testConfig.Account1.Password); err == nil {
|
|
t.Error("error expected and not received")
|
|
return
|
|
}
|
|
if err != status.ErrQueuedTxIDNotFound {
|
|
t.Errorf("unexpected error received: expected '%s', got: '%s'", status.ErrQueuedTxIDNotFound.Error(), err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestEvictionOfQueuedTransactions(t *testing.T) {
|
|
err := geth.PrepareTestNode()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// obtain reference to status backend
|
|
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
|
if err != nil {
|
|
t.Errorf("Test failed: LES service is not running: %v", err)
|
|
return
|
|
}
|
|
backend := lightEthereum.StatusBackend
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
|
return
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
completeQueuedTransaction := make(chan struct{}, 1)
|
|
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
|
|
|
|
// replace transaction notification handler
|
|
var txHash = common.Hash{}
|
|
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var envelope geth.SignalEnvelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == geth.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
t.Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
|
|
|
// next call is the very same one, but with the correct password
|
|
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
|
|
return
|
|
}
|
|
|
|
t.Logf("Transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
|
completeQueuedTransaction <- struct{}{} // so that timeout is aborted
|
|
}
|
|
})
|
|
|
|
txQueue := backend.TransactionQueue()
|
|
var i = 0
|
|
txIDs := [status.DefaultTxQueueCap + 5 + 10]status.QueuedTxID{}
|
|
backend.SetTransactionQueueHandler(func(queuedTx status.QueuedTx) {
|
|
t.Logf("%d. Transaction queued (queue size: %d): {id: %v}\n", i, txQueue.Count(), queuedTx.ID)
|
|
txIDs[i] = queuedTx.ID
|
|
i++
|
|
})
|
|
|
|
if txQueue.Count() != 0 {
|
|
t.Errorf("transaction count should be zero: %d", txQueue.Count())
|
|
return
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
|
|
}
|
|
time.Sleep(3 * time.Second)
|
|
|
|
t.Logf("Number of transactions queued: %d. Queue size (shouldn't be more than %d): %d", i, status.DefaultTxQueueCap, txQueue.Count())
|
|
|
|
if txQueue.Count() != 10 {
|
|
t.Errorf("transaction count should be 10: got %d", txQueue.Count())
|
|
return
|
|
}
|
|
|
|
for i := 0; i < status.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines
|
|
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
|
|
}
|
|
time.Sleep(5 * time.Second)
|
|
|
|
if txQueue.Count() > status.DefaultTxQueueCap {
|
|
t.Errorf("transaction count should be %d (or %d): got %d", status.DefaultTxQueueCap, status.DefaultTxQueueCap-1, txQueue.Count())
|
|
return
|
|
}
|
|
|
|
for _, txID := range txIDs {
|
|
txQueue.Remove(txID)
|
|
}
|
|
|
|
if txQueue.Count() != 0 {
|
|
t.Errorf("transaction count should be zero: %d", txQueue.Count())
|
|
return
|
|
}
|
|
}
|