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 }