Separating out Transaction Receipt functionality (#186)

sharding: separating out transaction receipt functionality
Former-commit-id: 1f581a36e282a5a7579f14f613a44946abbdf93d [formerly 58f934a8fb5f23c3e594d70039fc5d99815bc90c]
Former-commit-id: f0c9265a84c1f7a09ae3d37a2d4a8f74e2e25b96
This commit is contained in:
Nishant Das 2018-06-20 22:22:56 +08:00 committed by terence tsao
parent 346b9ae8aa
commit ed9db010ea
4 changed files with 241 additions and 9 deletions

View File

@ -151,18 +151,32 @@ func (s *SMCClient) SMCFilterer() *contracts.SMCFilterer {
return &s.smc.SMCFilterer
}
// WaitForTransaction waits for transaction to be mined and returns an error if it takes
// too long.
func (s *SMCClient) WaitForTransaction(ctx context.Context, hash common.Hash, durationInSeconds time.Duration) error {
ctxTimeout, cancel := context.WithTimeout(ctx, durationInSeconds*time.Second)
for pending, err := true, error(nil); pending; _, pending, err = s.client.TransactionByHash(ctxTimeout, hash) {
if err != nil {
cancel()
return fmt.Errorf("unable to retrieve transaction: %v", err)
}
if ctxTimeout.Err() != nil {
cancel()
return fmt.Errorf("transaction timed out, transaction was not able to be mined in the duration: %v", ctxTimeout.Err())
}
}
cancel()
ctxTimeout.Done()
log.Info(fmt.Sprintf("Transaction: %s has been mined", hash.Hex()))
return nil
}
// TransactionReceipt allows an SMCClient to retrieve transaction receipts on
// the mainchain by hash.
func (s *SMCClient) TransactionReceipt(hash common.Hash) (*types.Receipt, error) {
for pending, err := true, error(nil); pending; _, pending, err = s.client.TransactionByHash(context.Background(), hash) {
if err != nil {
return nil, fmt.Errorf("Unable to retrieve transaction: %v", err)
}
time.Sleep(1 * time.Second)
}
log.Info(fmt.Sprintf("Transaction: %s has been mined", hash.Hex()))
receipt, err := s.client.TransactionReceipt(context.Background(), hash)
if err != nil {
return nil, err

View File

@ -1,6 +1,200 @@
package mainchain
import "github.com/ethereum/go-ethereum/sharding"
import (
"context"
"fmt"
"math/big"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/sharding"
"github.com/ethereum/go-ethereum/sharding/contracts"
)
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
accountBalance1001Eth, _ = new(big.Int).SetString("1001000000000000000000", 10)
)
// Verifies that SMCCLient implements the sharding Service inteface.
var _ = sharding.Service(&SMCClient{})
// mockClient is struct to implement the smcClient methods for testing.
type mockClient struct {
smc *contracts.SMC
depositFlag bool
t *testing.T
backend *backends.SimulatedBackend
blockNumber *big.Int
}
// Mirrors the function in the main file, but instead of having a client to perform rpc calls
// it is replaced by the simulated backend.
func (m *mockClient) WaitForTransaction(ctx context.Context, hash common.Hash, durationInSeconds time.Duration) error {
var receipt *types.Receipt
ctxTimeout, cancel := context.WithTimeout(ctx, durationInSeconds*time.Second)
for err := error(nil); receipt == nil; receipt, err = m.backend.TransactionReceipt(ctxTimeout, hash) {
if err != nil {
cancel()
return fmt.Errorf("unable to retrieve transaction: %v", err)
}
if ctxTimeout.Err() != nil {
cancel()
return fmt.Errorf("transaction timed out, transaction was not able to be mined in the duration: %v", ctxTimeout.Err())
}
}
cancel()
ctxTimeout.Done()
log.Info(fmt.Sprintf("Transaction: %s has been mined", hash.Hex()))
return nil
}
// Creates and send Fake Transactions to the backend to be mined, takes in the context and
// the current blocknumber as an argument and returns the signed transaction after it has been sent.
func (m *mockClient) CreateAndSendFakeTx(ctx context.Context) (*types.Transaction, error) {
tx := types.NewTransaction(m.blockNumber.Uint64(), common.HexToAddress("0x"), nil, 50000, nil, nil)
signedtx, err := types.SignTx(tx, types.MakeSigner(&params.ChainConfig{}, m.blockNumber), key)
if err != nil {
return nil, err
}
err = m.backend.SendTransaction(ctx, signedtx)
if err != nil {
return nil, fmt.Errorf("unable to send transaction: %v", err)
}
return signedtx, nil
}
func (m *mockClient) Commit() {
m.backend.Commit()
m.blockNumber = big.NewInt(m.blockNumber.Int64() + 1)
}
func setup() *backends.SimulatedBackend {
backend := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: accountBalance1001Eth}})
return backend
}
// TestWaitForTransaction tests the WaitForTransaction function in the smcClient, however since for testing we do not have
// an inmemory rpc server to interact with the simulated backend, we have to define the function in the test rather than
// in `smc_client.go`.
// TODO: Test the function in the main file instead of defining it here if a rpc server for testing can be
// implemented.
func TestWaitForTransaction_TransactionNotMined(t *testing.T) {
backend := setup()
client := &mockClient{backend: backend, blockNumber: big.NewInt(0)}
ctx := context.Background()
timeout := time.Duration(1)
tx, err := client.CreateAndSendFakeTx(ctx)
if err != nil {
t.Error(err)
}
receipt, err := client.backend.TransactionReceipt(ctx, tx.Hash())
if receipt != nil {
t.Errorf("transaction mined despite backend not being committed: %v", receipt)
}
err = client.WaitForTransaction(ctx, tx.Hash(), timeout)
if err == nil {
t.Error("transaction is supposed to timeout and return a error")
}
}
func TestWaitForTransaction_IsMinedImmediately(t *testing.T) {
backend := setup()
client := &mockClient{backend: backend, blockNumber: big.NewInt(0)}
ctx := context.Background()
timeout := time.Duration(1)
var wg sync.WaitGroup
wg.Add(1)
tx, err := client.CreateAndSendFakeTx(ctx)
if err != nil {
t.Error(err)
}
// Tests transaction timing out when the block is mined immediately
// in the timeout period
go func() {
newErr := client.WaitForTransaction(ctx, tx.Hash(), timeout)
if newErr != nil {
t.Errorf("transaction timing out despite backend being committed: %v", newErr)
}
receipt, err := client.backend.TransactionReceipt(ctx, tx.Hash())
if err != nil {
t.Errorf("receipt could not be retrieved:%v", err)
}
if receipt == nil {
t.Error("receipt not found despite transaction being mined")
}
wg.Done()
}()
client.Commit()
wg.Wait()
}
func TestWaitForTransaction_TimesOut(t *testing.T) {
backend := setup()
client := &mockClient{backend: backend, blockNumber: big.NewInt(0)}
ctx := context.Background()
timeout := time.Duration(1)
var wg sync.WaitGroup
wg.Add(1)
tx, err := client.CreateAndSendFakeTx(ctx)
if err != nil {
t.Error(err)
}
go func() {
newErr := client.WaitForTransaction(ctx, tx.Hash(), timeout)
if newErr == nil {
t.Error("transaction not timing out despite backend being committed too late")
}
client.Commit()
wg.Done()
}()
wg.Wait()
}
func TestWaitForTransaction_IsCancelledWhenParentCtxCancelled(t *testing.T) {
backend := setup()
client := &mockClient{backend: backend, blockNumber: big.NewInt(0)}
ctx := context.Background()
timeout := time.Duration(1)
var wg sync.WaitGroup
wg.Add(1)
tx, err := client.CreateAndSendFakeTx(ctx)
if err != nil {
t.Error(err)
}
newCtx, cancel := context.WithCancel(ctx)
go func() {
newErr := client.WaitForTransaction(newCtx, tx.Hash(), timeout)
if newErr == nil {
t.Error("no error despite parent context being canceled")
}
wg.Done()
}()
cancel()
newCtx.Done()
wg.Wait()
}

View File

@ -1,6 +1,7 @@
package notary
import (
"context"
"math/big"
"testing"
@ -8,7 +9,9 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/sharding"
"github.com/ethereum/go-ethereum/sharding/contracts"
@ -47,6 +50,18 @@ func (s *smcClient) SMCTransactor() *contracts.SMCTransactor {
return &s.smc.SMCTransactor
}
func (s *smcClient) SMCFilterer() *contracts.SMCFilterer {
return &s.smc.SMCFilterer
}
func (s *smcClient) WaitForTransaction(ctx context.Context, hash common.Hash, durationInSeconds int64) error {
return nil
}
func (s *smcClient) TransactionReceipt(hash common.Hash) (*types.Receipt, error) {
return nil, nil
}
func (s *smcClient) CreateTXOpts(value *big.Int) (*bind.TransactOpts, error) {
txOpts := transactOpts()
txOpts.Value = value

View File

@ -1,6 +1,7 @@
package proposer
import (
"context"
"crypto/rand"
"math/big"
"testing"
@ -47,6 +48,14 @@ func (m *mockNode) SMCTransactor() *contracts.SMCTransactor {
return &m.smc.SMCTransactor
}
func (m *mockNode) WaitForTransaction(ctx context.Context, hash common.Hash, durationInSeconds int64) error {
return nil
}
func (m *mockNode) TransactionReceipt(hash common.Hash) (*types.Receipt, error) {
return nil, nil
}
func (m *mockNode) CreateTXOpts(value *big.Int) (*bind.TransactOpts, error) {
txOpts := transactOpts()
txOpts.Value = value