github.com/lmittmann/w3@v0.20.0/client_test.go (about)

     1  package w3_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"flag"
     8  	"math/big"
     9  	"strconv"
    10  	"testing"
    11  
    12  	"github.com/ethereum/go-ethereum"
    13  	"github.com/ethereum/go-ethereum/common"
    14  	"github.com/ethereum/go-ethereum/core/types"
    15  	"github.com/ethereum/go-ethereum/ethclient"
    16  	"github.com/ethereum/go-ethereum/rpc"
    17  	"github.com/google/go-cmp/cmp"
    18  	"github.com/lmittmann/w3"
    19  	"github.com/lmittmann/w3/internal"
    20  	"github.com/lmittmann/w3/module/eth"
    21  	"github.com/lmittmann/w3/rpctest"
    22  	"github.com/lmittmann/w3/w3types"
    23  )
    24  
    25  var (
    26  	benchRPC = flag.String("benchRPC", "", "RPC endpoint for benchmark")
    27  
    28  	jsonCalls1 = `> {"jsonrpc":"2.0","id":1}` + "\n" +
    29  		`< {"jsonrpc":"2.0","id":1,"result":"0x1"}`
    30  	jsonCalls2 = `> [{"jsonrpc":"2.0","id":1},{"jsonrpc":"2.0","id":2}]` + "\n" +
    31  		`< [{"jsonrpc":"2.0","id":1,"result":"0x1"},{"jsonrpc":"2.0","id":2,"result":"0x1"}]`
    32  )
    33  
    34  func TestClientCall(t *testing.T) {
    35  	tests := []struct {
    36  		Buf     *bytes.Buffer
    37  		Calls   []w3types.RPCCaller
    38  		WantErr error
    39  	}{
    40  		{
    41  			Buf:   bytes.NewBufferString(jsonCalls1),
    42  			Calls: []w3types.RPCCaller{&testCaller{}},
    43  		},
    44  		{
    45  			Buf:     bytes.NewBufferString(jsonCalls1),
    46  			Calls:   []w3types.RPCCaller{&testCaller{RequestErr: errors.New("err")}},
    47  			WantErr: errors.New("err"),
    48  		},
    49  		{
    50  			Buf:     bytes.NewBufferString(jsonCalls1),
    51  			Calls:   []w3types.RPCCaller{&testCaller{ReturnErr: errors.New("err")}},
    52  			WantErr: errors.New("w3: call failed: err"),
    53  		},
    54  		{
    55  			Buf: bytes.NewBufferString(jsonCalls2),
    56  			Calls: []w3types.RPCCaller{
    57  				&testCaller{RequestErr: errors.New("err")},
    58  				&testCaller{},
    59  			},
    60  			WantErr: errors.New("err"),
    61  		},
    62  		{
    63  			Buf: bytes.NewBufferString(jsonCalls2),
    64  			Calls: []w3types.RPCCaller{
    65  				&testCaller{ReturnErr: errors.New("err")},
    66  				&testCaller{},
    67  			},
    68  			WantErr: errors.New("w3: 1 call failed:\ncall[0]: err"),
    69  		},
    70  		{
    71  			Buf: bytes.NewBufferString(jsonCalls2),
    72  			Calls: []w3types.RPCCaller{
    73  				&testCaller{},
    74  				&testCaller{ReturnErr: errors.New("err")},
    75  			},
    76  			WantErr: errors.New("w3: 1 call failed:\ncall[1]: err"),
    77  		},
    78  		{
    79  			Buf: bytes.NewBufferString(jsonCalls2),
    80  			Calls: []w3types.RPCCaller{
    81  				&testCaller{ReturnErr: errors.New("err")},
    82  				&testCaller{ReturnErr: errors.New("err")},
    83  			},
    84  			WantErr: errors.New("w3: 2 calls failed:\ncall[0]: err\ncall[1]: err"),
    85  		},
    86  	}
    87  
    88  	for i, test := range tests {
    89  		t.Run(strconv.Itoa(i), func(t *testing.T) {
    90  			srv := rpctest.NewServer(t, test.Buf)
    91  
    92  			client, err := w3.Dial(srv.URL())
    93  			if err != nil {
    94  				t.Fatalf("Failed to connect to test RPC endpoint: %v", err)
    95  			}
    96  
    97  			err = client.Call(test.Calls...)
    98  			if diff := cmp.Diff(test.WantErr, err,
    99  				internal.EquateErrors(),
   100  			); diff != "" {
   101  				t.Fatalf("(-want, +got)\n%s", diff)
   102  			}
   103  		})
   104  	}
   105  }
   106  
   107  func TestClientCall_CallErrors(t *testing.T) {
   108  	srv := rpctest.NewServer(t, bytes.NewBufferString(jsonCalls2))
   109  
   110  	client, err := w3.Dial(srv.URL())
   111  	if err != nil {
   112  		t.Fatalf("Failed to connect to test RPC endpoint: %v", err)
   113  	}
   114  
   115  	err = client.Call(&testCaller{}, &testCaller{ReturnErr: errors.New("err")})
   116  	if err == nil {
   117  		t.Fatal("Want error")
   118  	}
   119  	if !errors.Is(err, w3.CallErrors{}) {
   120  		t.Fatalf("Want w3.CallErrors, got %T", err)
   121  	}
   122  	callErrs := err.(w3.CallErrors)
   123  	if callErrs[0] != nil {
   124  		t.Errorf("callErrs[0]: want <nil>, got %v", callErrs[0])
   125  	}
   126  	if callErrs[1] == nil || callErrs[1].Error() != "err" {
   127  		t.Errorf(`callErrs[1]: want "err", got %v`, callErrs[1])
   128  	}
   129  }
   130  
   131  type testCaller struct {
   132  	RequestErr error
   133  	ReturnErr  error
   134  }
   135  
   136  func (c *testCaller) CreateRequest() (elem rpc.BatchElem, err error) {
   137  	return rpc.BatchElem{}, c.RequestErr
   138  }
   139  
   140  func (c *testCaller) HandleResponse(elem rpc.BatchElem) (err error) {
   141  	return c.ReturnErr
   142  }
   143  
   144  func BenchmarkCall_BalanceNonce(b *testing.B) {
   145  	if *benchRPC == "" {
   146  		b.Skipf("Missing -benchRPC")
   147  	}
   148  
   149  	w3Client := w3.MustDial(*benchRPC)
   150  	defer w3Client.Close()
   151  
   152  	ethClient, _ := ethclient.Dial(*benchRPC)
   153  	defer ethClient.Close()
   154  
   155  	addr := common.Address{}
   156  
   157  	b.Run("Batch", func(b *testing.B) {
   158  		var (
   159  			nonce   uint64
   160  			balance *big.Int
   161  		)
   162  		for range b.N {
   163  			w3Client.Call(
   164  				eth.Nonce(addr, nil).Returns(&nonce),
   165  				eth.Balance(addr, nil).Returns(&balance),
   166  			)
   167  		}
   168  	})
   169  
   170  	b.Run("Sequential", func(b *testing.B) {
   171  		for range b.N {
   172  			ethClient.NonceAt(context.Background(), addr, nil)
   173  			ethClient.BalanceAt(context.Background(), addr, nil)
   174  		}
   175  	})
   176  }
   177  
   178  func BenchmarkCall_Balance100(b *testing.B) {
   179  	if *benchRPC == "" {
   180  		b.Skipf("Missing -benchRPC")
   181  	}
   182  
   183  	w3Client := w3.MustDial(*benchRPC)
   184  	defer w3Client.Close()
   185  
   186  	ethClient, _ := ethclient.Dial(*benchRPC)
   187  	defer ethClient.Close()
   188  
   189  	addr100 := make([]common.Address, 100)
   190  	for i := range len(addr100) {
   191  		addr100[i] = common.BigToAddress(big.NewInt(int64(i)))
   192  	}
   193  
   194  	b.Run("Batch", func(b *testing.B) {
   195  		var balance *big.Int
   196  		for range b.N {
   197  			requests := make([]w3types.RPCCaller, len(addr100))
   198  			for j := range len(requests) {
   199  				requests[j] = eth.Balance(addr100[j], nil).Returns(&balance)
   200  			}
   201  			w3Client.Call(requests...)
   202  		}
   203  	})
   204  
   205  	b.Run("Sequential", func(b *testing.B) {
   206  		for range b.N {
   207  			for _, addr := range addr100 {
   208  				ethClient.BalanceAt(context.Background(), addr, nil)
   209  			}
   210  		}
   211  	})
   212  }
   213  
   214  func BenchmarkCall_BalanceOf100(b *testing.B) {
   215  	if *benchRPC == "" {
   216  		b.Skipf("Missing -benchRPC")
   217  	}
   218  
   219  	w3Client := w3.MustDial(*benchRPC)
   220  	defer w3Client.Close()
   221  
   222  	ethClient, _ := ethclient.Dial(*benchRPC)
   223  	defer ethClient.Close()
   224  
   225  	addr100 := make([]common.Address, 100)
   226  	for i := range len(addr100) {
   227  		addr100[i] = common.BigToAddress(big.NewInt(int64(i)))
   228  	}
   229  
   230  	funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256")
   231  	addrWeth9 := w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
   232  
   233  	b.Run("Batch", func(b *testing.B) {
   234  		var balance big.Int
   235  		for range b.N {
   236  			requests := make([]w3types.RPCCaller, len(addr100))
   237  			for j := range len(requests) {
   238  				requests[j] = eth.CallFunc(addrWeth9, funcBalanceOf, addr100[j]).Returns(&balance)
   239  			}
   240  			w3Client.Call(requests...)
   241  		}
   242  	})
   243  
   244  	b.Run("Sequential", func(b *testing.B) {
   245  		for range b.N {
   246  			for _, addr := range addr100 {
   247  				input, err := funcBalanceOf.EncodeArgs(addr)
   248  				if err != nil {
   249  					b.Fatalf("Failed to encode args: %v", err)
   250  				}
   251  				ethClient.CallContract(context.Background(), ethereum.CallMsg{
   252  					To:   &addrWeth9,
   253  					Data: input,
   254  				}, nil)
   255  			}
   256  		}
   257  	})
   258  }
   259  
   260  func BenchmarkCall_Block100(b *testing.B) {
   261  	if *benchRPC == "" {
   262  		b.Skipf("Missing -benchRPC")
   263  	}
   264  
   265  	w3Client := w3.MustDial(*benchRPC)
   266  	defer w3Client.Close()
   267  
   268  	ethClient, _ := ethclient.Dial(*benchRPC)
   269  	defer ethClient.Close()
   270  
   271  	block100 := make([]*big.Int, 100)
   272  	for i := range len(block100) {
   273  		block100[i] = big.NewInt(int64(14_000_000 + i))
   274  	}
   275  
   276  	b.Run("Batch", func(b *testing.B) {
   277  		var block *types.Block
   278  		for range b.N {
   279  			requests := make([]w3types.RPCCaller, len(block100))
   280  			for j := range len(requests) {
   281  				requests[j] = eth.BlockByNumber(block100[j]).Returns(&block)
   282  			}
   283  			w3Client.Call(requests...)
   284  		}
   285  	})
   286  
   287  	b.Run("Sequential", func(b *testing.B) {
   288  		for range b.N {
   289  			for _, number := range block100 {
   290  				ethClient.BlockByNumber(context.Background(), number)
   291  			}
   292  		}
   293  	})
   294  }