fixes issue when non-originator can complete a tx

This commit is contained in:
Victor Farazdagi 2016-12-15 12:49:05 +03:00
parent 80510c02d2
commit 87e345c1cc
8 changed files with 138 additions and 10 deletions

View File

@ -529,6 +529,12 @@ func testCompleteTransaction(t *testing.T) bool {
// reset queue
backend.TransactionQueue().Reset()
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return false
}
// make sure you panic if transaction complete doesn't return
queuedTxCompleted := make(chan struct{}, 1)
abortPanic := make(chan struct{}, 1)
@ -608,6 +614,12 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
// reset queue
backend.TransactionQueue().Reset()
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return false
}
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIds := make(chan string, testTxCount)
@ -737,6 +749,12 @@ func testDiscardTransaction(t *testing.T) bool {
// reset queue
backend.TransactionQueue().Reset()
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return false
}
// make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1)
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestDiscardQueuedTransactions")
@ -851,6 +869,12 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
// reset queue
backend.TransactionQueue().Reset()
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return false
}
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIds := make(chan string, testTxCount)

View File

@ -320,3 +320,11 @@ func (m *NodeManager) populateStaticPeers() {
m.AddPeer(enode)
}
}
func (k *SelectedExtKey) Hex() string {
if k == nil {
return "0x0"
}
return k.Address.Hex()
}

View File

@ -86,7 +86,10 @@ func CompleteTransaction(id, password string) (common.Hash, error) {
backend := lightEthereum.StatusBackend
return backend.CompleteQueuedTransaction(status.QueuedTxId(id), password)
ctx := context.Background()
ctx = context.WithValue(ctx, status.SelectedAccountKey, NodeManagerInstance().SelectedAccount.Hex())
return backend.CompleteQueuedTransaction(ctx, status.QueuedTxId(id), password)
}
func CompleteTransactions(ids, password string) map[string]RawCompleteTransactionResult {

View File

@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/les/status"
"github.com/ethereum/go-ethereum/rpc"
@ -28,6 +29,13 @@ func TestQueuedTransactions(t *testing.T) {
}
backend := lightEthereum.StatusBackend
// create an account
sampleAddress, _, _, err := geth.CreateAccount(newAccountPassword)
if err != nil {
t.Errorf("could not create account: %v", err)
return
}
// make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1)
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
@ -45,6 +53,27 @@ func TestQueuedTransactions(t *testing.T) {
t.Logf("transaction queued (will be completed in 5 secs): {id: %s}\n", event["id"].(string))
time.Sleep(5 * time.Second)
// the first call will fail (we are not logged in, but trying to complete tx)
if txHash, err = geth.CompleteTransaction(event["id"].(string), testAddressPassword); err != status.ErrInvalidCompleteTxSender {
t.Errorf("expected error on queued transation[%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, newAccountPassword); err != nil {
t.Errorf("cannot select account: %v", sampleAddress)
return
}
if txHash, err = geth.CompleteTransaction(event["id"].(string), testAddressPassword); err != status.ErrInvalidCompleteTxSender {
t.Errorf("expected error on queued transation[%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(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return
}
if txHash, err = geth.CompleteTransaction(event["id"].(string), testAddressPassword); err != nil {
t.Errorf("cannot complete queued transation[%v]: %v", event["id"], err)
return
@ -98,6 +127,12 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
}
backend := lightEthereum.StatusBackend
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return
}
// make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1)
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
@ -119,8 +154,8 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
// 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, testAddressPassword+"wrong"); err == nil {
t.Error("expects wrong password error, but call succeeded")
if _, err = geth.CompleteTransaction(txId, testAddressPassword+"wrong"); err != accounts.ErrDecrypt {
t.Errorf("expects wrong password error, but call succeeded (or got another error: %v)", err)
return
}
@ -222,6 +257,12 @@ func TestDiscardQueuedTransactions(t *testing.T) {
// reset queue
backend.TransactionQueue().Reset()
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return
}
// make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1)
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestDiscardQueuedTransactions")
@ -337,6 +378,12 @@ func TestCompleteMultipleQueuedTransactions(t *testing.T) {
// reset queue
backend.TransactionQueue().Reset()
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return
}
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIds := make(chan string, testTxCount)
@ -462,6 +509,12 @@ func TestDiscardMultipleQueuedTransactions(t *testing.T) {
// reset queue
backend.TransactionQueue().Reset()
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return
}
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIds := make(chan string, testTxCount)
@ -606,6 +659,12 @@ func TestNonExistentQueuedTransactions(t *testing.T) {
return
}
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return
}
// make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1)
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
@ -660,6 +719,12 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
}
backend := lightEthereum.StatusBackend
// log into account from which transactions will be sent
if err := geth.SelectAccount(testAddress, testAddressPassword); err != nil {
t.Errorf("cannot select account: %v", testAddress)
return
}
// make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1)
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")

View File

@ -208,6 +208,12 @@ func TestJailSendQueuedTransaction(t *testing.T) {
return
}
// log into account from which transactions will be sent
if err := geth.SelectAccount(TEST_ADDRESS, TEST_ADDRESS_PASSWORD); err != nil {
t.Errorf("cannot select account: %v", TEST_ADDRESS)
return
}
txParams := `{
"from": "` + TEST_ADDRESS + `",
"to": "0xf82da7547534045b4e00442bc89e16186cf8c272",

View File

@ -1126,6 +1126,16 @@ func (s *PublicTransactionPoolAPI) CompleteQueuedTransaction(ctx context.Context
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 {
glog.V(logger.Info).Infof("Failed to complete tx by %s (when logged in as %s)", args.From.Hex(), selectedAccountAddress)
return common.Hash{}, status.ErrInvalidCompleteTxSender
}
nonce, err := s.b.GetPoolNonce(ctx, args.From)
if err != nil {
return common.Hash{}, err

View File

@ -85,13 +85,13 @@ func (b *StatusBackend) SendTransaction(ctx context.Context, args status.SendTxA
}
// CompleteQueuedTransaction wraps call to PublicTransactionPoolAPI.CompleteQueuedTransaction
func (b *StatusBackend) CompleteQueuedTransaction(id status.QueuedTxId, passphrase string) (common.Hash, error) {
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(context.Background(), SendTxArgs(queuedTx.Args), passphrase)
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 == accounts.ErrDecrypt {
@ -99,6 +99,12 @@ func (b *StatusBackend) CompleteQueuedTransaction(id status.QueuedTxId, passphra
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

View File

@ -14,12 +14,14 @@ 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
@ -177,8 +179,12 @@ func (q *TxQueue) NotifyOnQueuedTxReturn(queuedTx *QueuedTx, err error) {
return
}
// remove from queue on any error (except for password related one) and propagate
if err != accounts.ErrDecrypt {
// remove from queue on any error (except for transient ones) and propagate
transientErrs := map[error]bool{
accounts.ErrDecrypt: true, // wrong password
ErrInvalidCompleteTxSender: true, // completing tx create from another account
}
if !transientErrs[err] { // remove only on unrecoverable errors
q.Remove(queuedTx.Id)
}