Merge pull request #60 from farazdagi/feature/tx-multi-actions
DiscardTransactions() + CompleteTransactions()
This commit is contained in:
commit
bc8ec4500e
2
Makefile
2
Makefile
|
@ -59,6 +59,8 @@ test-all:
|
|||
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
|
||||
build/env.sh go test -coverprofile=coverage.out -covermode=set ./extkeys
|
||||
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
|
||||
build/env.sh go test -coverprofile=coverage.out -covermode=set ./cmd/status
|
||||
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
|
||||
@build/env.sh go tool cover -html=coverage-all.out -o coverage.html
|
||||
@build/env.sh go tool cover -func=coverage-all.out
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ func CompleteTransaction(id, password *C.char) *C.char {
|
|||
}
|
||||
|
||||
out := geth.CompleteTransactionResult{
|
||||
Id: C.GoString(id),
|
||||
Hash: txHash.Hex(),
|
||||
Error: errString,
|
||||
}
|
||||
|
@ -158,6 +159,27 @@ func CompleteTransaction(id, password *C.char) *C.char {
|
|||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export CompleteTransactions
|
||||
func CompleteTransactions(ids, password *C.char) *C.char {
|
||||
out := geth.CompleteTransactionsResult{}
|
||||
out.Results = make(map[string]geth.CompleteTransactionResult)
|
||||
|
||||
results := geth.CompleteTransactions(C.GoString(ids), C.GoString(password))
|
||||
for txId, result := range results {
|
||||
txResult := geth.CompleteTransactionResult{
|
||||
Id: txId,
|
||||
Hash: result.Hash.Hex(),
|
||||
}
|
||||
if result.Error != nil {
|
||||
txResult.Error = result.Error.Error()
|
||||
}
|
||||
out.Results[txId] = txResult
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export DiscardTransaction
|
||||
func DiscardTransaction(id *C.char) *C.char {
|
||||
err := geth.DiscardTransaction(C.GoString(id))
|
||||
|
@ -177,6 +199,26 @@ func DiscardTransaction(id *C.char) *C.char {
|
|||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export DiscardTransactions
|
||||
func DiscardTransactions(ids *C.char) *C.char {
|
||||
out := geth.DiscardTransactionsResult{}
|
||||
out.Results = make(map[string]geth.DiscardTransactionResult)
|
||||
|
||||
results := geth.DiscardTransactions(C.GoString(ids))
|
||||
for txId, result := range results {
|
||||
txResult := geth.DiscardTransactionResult{
|
||||
Id: txId,
|
||||
}
|
||||
if result.Error != nil {
|
||||
txResult.Error = result.Error.Error()
|
||||
}
|
||||
out.Results[txId] = txResult
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export StartNode
|
||||
func StartNode(datadir *C.char) *C.char {
|
||||
// This starts a geth node with the given datadir
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
// the actual test functions are in non-_test.go files (so that they can use cgo i.e. import "C")
|
||||
// the only intent of these wrappers is for gotest can find what tests are exposed.
|
||||
func TestExportedAPI(t *testing.T) {
|
||||
allTestsDone := make(chan struct{}, 1)
|
||||
go testExportedAPI(t, allTestsDone)
|
||||
|
||||
<- allTestsDone
|
||||
}
|
|
@ -0,0 +1,384 @@
|
|||
package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/status-im/status-go/geth"
|
||||
)
|
||||
|
||||
const (
|
||||
testDataDir = "../../.ethereumtest"
|
||||
testNodeSyncSeconds = 120
|
||||
testAddress = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
|
||||
testAddressPassword = "asdf"
|
||||
newAccountPassword = "badpassword"
|
||||
testAddress1 = "0xf82da7547534045b4e00442bc89e16186cf8c272"
|
||||
)
|
||||
|
||||
func testExportedAPI(t *testing.T, done chan struct{}) {
|
||||
<-startTestNode(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func(t *testing.T) bool
|
||||
}{
|
||||
{
|
||||
"test complete multiple queued transactions",
|
||||
testCompleteMultipleQueuedTransactions,
|
||||
},
|
||||
{
|
||||
"test discard multiple queued transactions",
|
||||
testDiscardMultipleQueuedTransactions,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if ok := test.fn(t); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
done <- struct{}{}
|
||||
}
|
||||
|
||||
func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.GetNodeManager().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()
|
||||
|
||||
// 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.GethEvent
|
||||
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(testAddress),
|
||||
To: geth.ToAddress(testAddress1),
|
||||
Value: rpc.NewHexNumber(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
|
||||
json.Unmarshal([]byte(txIdStrings), &parsedIds)
|
||||
|
||||
parsedIds = append(parsedIds, "invalid-tx-id")
|
||||
updatedTxIdStrings, _ := json.Marshal(parsedIds)
|
||||
|
||||
// complete
|
||||
resultsString := CompleteTransactions(C.CString(string(updatedTxIdStrings)), C.CString(testAddressPassword))
|
||||
resultsStruct := geth.CompleteTransactionsResult{}
|
||||
json.Unmarshal([]byte(C.GoString(resultsString)), &resultsStruct)
|
||||
results := resultsStruct.Results
|
||||
|
||||
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error != "transaction hash not found" {
|
||||
t.Errorf("cannot complete txs: %v", results)
|
||||
return
|
||||
}
|
||||
for txId, txResult := range results {
|
||||
if txId != txResult.Id {
|
||||
t.Errorf("tx id not set in result: expected id is %s", txId)
|
||||
return
|
||||
}
|
||||
if txResult.Error != "" && txId != "invalid-tx-id" {
|
||||
t.Errorf("invalid error for %s", txId)
|
||||
return
|
||||
}
|
||||
if txResult.Hash == "0x0000000000000000000000000000000000000000000000000000000000000000" && txId != "invalid-tx-id" {
|
||||
t.Errorf("invalid hash (expected non empty hash): %s", txId)
|
||||
return
|
||||
}
|
||||
|
||||
if txResult.Hash != "0x0000000000000000000000000000000000000000000000000000000000000000" {
|
||||
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txResult.Hash)
|
||||
}
|
||||
}
|
||||
|
||||
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 false
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.GetNodeManager().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()
|
||||
|
||||
// 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.GethEvent
|
||||
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(testAddress),
|
||||
To: geth.ToAddress(testAddress1),
|
||||
Value: rpc.NewHexNumber(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
|
||||
json.Unmarshal([]byte(txIdStrings), &parsedIds)
|
||||
|
||||
parsedIds = append(parsedIds, "invalid-tx-id")
|
||||
updatedTxIdStrings, _ := json.Marshal(parsedIds)
|
||||
|
||||
// discard
|
||||
discardResultsString := DiscardTransactions(C.CString(string(updatedTxIdStrings)))
|
||||
discardResultsStruct := geth.DiscardTransactionsResult{}
|
||||
json.Unmarshal([]byte(C.GoString(discardResultsString)), &discardResultsStruct)
|
||||
discardResults := discardResultsStruct.Results
|
||||
|
||||
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error != "transaction hash not found" {
|
||||
t.Errorf("cannot discard txs: %v", discardResults)
|
||||
return
|
||||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
completeResultsString := CompleteTransactions(C.CString(string(updatedTxIdStrings)), C.CString(testAddressPassword))
|
||||
completeResultsStruct := geth.CompleteTransactionsResult{}
|
||||
json.Unmarshal([]byte(C.GoString(completeResultsString)), &completeResultsStruct)
|
||||
completeResults := completeResultsStruct.Results
|
||||
|
||||
if len(completeResults) != (testTxCount + 1) {
|
||||
t.Error("unexpected number of errors (call to CompleteTransaction should not succeed)")
|
||||
}
|
||||
for txId, txResult := range completeResults {
|
||||
if txId != txResult.Id {
|
||||
t.Errorf("tx id not set in result: expected id is %s", txId)
|
||||
return
|
||||
}
|
||||
if txResult.Error != "transaction hash not found" {
|
||||
t.Errorf("invalid error for %s", txResult.Hash)
|
||||
return
|
||||
}
|
||||
if txResult.Hash != "0x0000000000000000000000000000000000000000000000000000000000000000" {
|
||||
t.Errorf("invalid hash (expected zero): %s", txResult.Hash)
|
||||
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 false
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func startTestNode(t *testing.T) <-chan struct{} {
|
||||
syncRequired := false
|
||||
if _, err := os.Stat(filepath.Join(testDataDir, "testnet")); os.IsNotExist(err) {
|
||||
syncRequired = true
|
||||
}
|
||||
|
||||
waitForNodeStart := make(chan struct{}, 1)
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
t.Log(jsonEvent)
|
||||
var envelope geth.GethEvent
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
}
|
||||
if envelope.Type == geth.EventNodeStarted {
|
||||
if syncRequired {
|
||||
t.Logf("Sync is required, it will take %d seconds", testNodeSyncSeconds)
|
||||
time.Sleep(testNodeSyncSeconds * time.Second) // LES syncs headers, so that we are up do date when it is done
|
||||
} else {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
// now we can proceed with tests
|
||||
waitForNodeStart <- struct{}{}
|
||||
}
|
||||
})
|
||||
|
||||
response := StartNode(C.CString(testDataDir))
|
||||
err := geth.JSONError{}
|
||||
|
||||
json.Unmarshal([]byte(C.GoString(response)), &err)
|
||||
if err.Error != "" {
|
||||
t.Error("cannot start node")
|
||||
}
|
||||
|
||||
return waitForNodeStart
|
||||
}
|
|
@ -13,6 +13,7 @@ const (
|
|||
testAddress = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
|
||||
testAddressPassword = "asdf"
|
||||
newAccountPassword = "badpassword"
|
||||
testAddress1 = "0xf82da7547534045b4e00442bc89e16186cf8c272"
|
||||
|
||||
whisperMessage1 = "test message 1 (K1 -> K1)"
|
||||
whisperMessage2 = "test message 2 (K1 -> '')"
|
||||
|
|
|
@ -102,6 +102,28 @@ func CompleteTransaction(id, password string) (common.Hash, error) {
|
|||
return backend.CompleteQueuedTransaction(status.QueuedTxId(id), password)
|
||||
}
|
||||
|
||||
func CompleteTransactions(ids, password string) map[string]RawCompleteTransactionResult {
|
||||
results := make(map[string]RawCompleteTransactionResult)
|
||||
|
||||
parsedIds, err := parseJSONArray(ids)
|
||||
if err != nil {
|
||||
results["none"] = RawCompleteTransactionResult{
|
||||
Error: err,
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
for _, txId := range parsedIds {
|
||||
txHash, txErr := CompleteTransaction(txId, password)
|
||||
results[txId] = RawCompleteTransactionResult{
|
||||
Hash: txHash,
|
||||
Error: txErr,
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func DiscardTransaction(id string) error {
|
||||
lightEthereum, err := GetNodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
|
@ -113,6 +135,30 @@ func DiscardTransaction(id string) error {
|
|||
return backend.DiscardQueuedTransaction(status.QueuedTxId(id))
|
||||
}
|
||||
|
||||
func DiscardTransactions(ids string) map[string]RawDiscardTransactionResult {
|
||||
var parsedIds []string
|
||||
results := make(map[string]RawDiscardTransactionResult)
|
||||
|
||||
parsedIds, err := parseJSONArray(ids)
|
||||
if err != nil {
|
||||
results["none"] = RawDiscardTransactionResult{
|
||||
Error: err,
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
for _, txId := range parsedIds {
|
||||
err := DiscardTransaction(txId)
|
||||
if err != nil {
|
||||
results[txId] = RawDiscardTransactionResult{
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func messageIdFromContext(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
|
@ -232,3 +278,13 @@ func sendTxArgsFromRPCCall(req RPCCall) status.SendTxArgs {
|
|||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func parseJSONArray(items string) ([]string, error) {
|
||||
var parsedItems []string
|
||||
err := json.Unmarshal([]byte(items), &parsedItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsedItems, nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
@ -21,19 +20,6 @@ func TestQueuedTransactions(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
accountManager, err := geth.GetNodeManager().AccountManager()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create an account
|
||||
address, _, _, err := geth.CreateAccount(newAccountPassword)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.GetNodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
|
@ -69,23 +55,10 @@ func TestQueuedTransactions(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
// send from the same test account (which is guaranteed to have ether)
|
||||
from, err := utils.MakeAddress(accountManager, testAddress)
|
||||
if err != nil {
|
||||
t.Errorf("could not retrieve account from address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
to, err := utils.MakeAddress(accountManager, address)
|
||||
if err != nil {
|
||||
t.Errorf("could not retrieve account from address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: from.Address,
|
||||
To: &to.Address,
|
||||
From: geth.FromAddress(testAddress),
|
||||
To: geth.ToAddress(testAddress1),
|
||||
Value: rpc.NewHexNumber(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -117,19 +90,6 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
accountManager, err := geth.GetNodeManager().AccountManager()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create an account
|
||||
address, _, _, err := geth.CreateAccount(newAccountPassword)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.GetNodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
|
@ -209,23 +169,10 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
// send from the same test account (which is guaranteed to have ether)
|
||||
from, err := utils.MakeAddress(accountManager, testAddress)
|
||||
if err != nil {
|
||||
t.Errorf("could not retrieve account from address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
to, err := utils.MakeAddress(accountManager, address)
|
||||
if err != nil {
|
||||
t.Errorf("could not retrieve account from address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// this call blocks, and should return on *second* attempt to CompleteTransaction (w/ the correct password)
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: from.Address,
|
||||
To: &to.Address,
|
||||
From: geth.FromAddress(testAddress),
|
||||
To: geth.ToAddress(testAddress1),
|
||||
Value: rpc.NewHexNumber(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -264,19 +211,6 @@ func TestDiscardQueuedTransactions(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
accountManager, err := geth.GetNodeManager().AccountManager()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create an account
|
||||
address, _, _, err := geth.CreateAccount(newAccountPassword)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.GetNodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
|
@ -355,27 +289,14 @@ func TestDiscardQueuedTransactions(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
// send from the same test account (which is guaranteed to have ether)
|
||||
from, err := utils.MakeAddress(accountManager, testAddress)
|
||||
if err != nil {
|
||||
t.Errorf("could not retrieve account from address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
to, err := utils.MakeAddress(accountManager, address)
|
||||
if err != nil {
|
||||
t.Errorf("could not retrieve account from address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() is called
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: from.Address,
|
||||
To: &to.Address,
|
||||
From: geth.FromAddress(testAddress),
|
||||
To: geth.ToAddress(testAddress1),
|
||||
Value: rpc.NewHexNumber(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != status.ErrQueuedTxDiscarded {
|
||||
t.Errorf("expeced error not thrown: %v", err)
|
||||
t.Errorf("expected error not thrown: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -398,6 +319,286 @@ func TestDiscardQueuedTransactions(t *testing.T) {
|
|||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
func TestCompleteMultipleQueuedTransactions(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.GetNodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
// 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.GethEvent
|
||||
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(testAddress),
|
||||
To: geth.ToAddress(testAddress1),
|
||||
Value: rpc.NewHexNumber(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
|
||||
json.Unmarshal([]byte(txIdStrings), &parsedIds)
|
||||
|
||||
parsedIds = append(parsedIds, "invalid-tx-id")
|
||||
updatedTxIdStrings, _ := json.Marshal(parsedIds)
|
||||
|
||||
// complete
|
||||
results := geth.CompleteTransactions(string(updatedTxIdStrings), testAddressPassword)
|
||||
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.GetNodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
// 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.GethEvent
|
||||
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(testAddress),
|
||||
To: geth.ToAddress(testAddress1),
|
||||
Value: rpc.NewHexNumber(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
|
||||
json.Unmarshal([]byte(txIdStrings), &parsedIds)
|
||||
|
||||
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), testAddressPassword)
|
||||
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 {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package geth
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
)
|
||||
|
||||
|
@ -49,15 +50,33 @@ type ReturnSendTransactionEvent struct {
|
|||
}
|
||||
|
||||
type CompleteTransactionResult struct {
|
||||
Id string `json:"id"`
|
||||
Hash string `json:"hash"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type RawCompleteTransactionResult struct {
|
||||
Hash common.Hash
|
||||
Error error
|
||||
}
|
||||
|
||||
type CompleteTransactionsResult struct {
|
||||
Results map[string]CompleteTransactionResult `json:"results"`
|
||||
}
|
||||
|
||||
type RawDiscardTransactionResult struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
type DiscardTransactionResult struct {
|
||||
Id string `json:"id"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type DiscardTransactionsResult struct {
|
||||
Results map[string]DiscardTransactionResult `json:"results"`
|
||||
}
|
||||
|
||||
type GethEvent struct {
|
||||
Type string `json:"type"`
|
||||
Event interface{} `json:"event"`
|
||||
|
|
|
@ -15,6 +15,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
@ -23,7 +25,7 @@ var muPrepareTestNode sync.Mutex
|
|||
|
||||
const (
|
||||
TestDataDir = "../.ethereumtest"
|
||||
TestNodeSyncSeconds = 60
|
||||
TestNodeSyncSeconds = 120
|
||||
)
|
||||
|
||||
type NodeNotificationHandler func(jsonEvent string)
|
||||
|
@ -177,3 +179,31 @@ func PanicAfter(waitSeconds time.Duration, abort chan struct{}, desc string) {
|
|||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func FromAddress(accountAddress string) common.Address {
|
||||
accountManager, err := GetNodeManager().AccountManager()
|
||||
if err != nil {
|
||||
return common.Address{}
|
||||
}
|
||||
|
||||
from, err := utils.MakeAddress(accountManager, accountAddress)
|
||||
if err != nil {
|
||||
return common.Address{}
|
||||
}
|
||||
|
||||
return from.Address
|
||||
}
|
||||
|
||||
func ToAddress(accountAddress string) *common.Address {
|
||||
accountManager, err := GetNodeManager().AccountManager()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
to, err := utils.MakeAddress(accountManager, accountAddress)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &to.Address
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue