Skip to content

Commit 4b24ebc

Browse files
committed
Merge branch 'main' into fund-testnet
2 parents 2016e62 + d9c773e commit 4b24ebc

File tree

9 files changed

+260
-127
lines changed

9 files changed

+260
-127
lines changed

book/src/libs/seth.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -851,25 +851,27 @@ Additionally, when the client is build anc `cfg.ReadOnly = true` is set, we will
851851

852852
## ABI Finder
853853

854-
In order to be able to decode and trace transactions and calls between smart contracts we need their ABIs. Unfortunately it might happen that two or more contracts have methods with the same signatures, which might result in incorrect tracing. To make that problem less severe we have decided to add a single point of entry for contract deployment in Seth as that way we always know what contract is deployed at which address and thus avoid incorrect tracing due to potentially ambiguous method signatures.
854+
In order to be able to decode and trace transactions and calls between smart contracts we need their ABIs. Unfortunately, it might happen that two or more contracts have methods with the same signatures, which might result in incorrect tracing. To make that problem less severe we have decided to add a single point of entry for contract deployment in Seth to track what contract is deployed at which address and thus minimise incorrect tracing due to potentially ambiguous method signatures.
855855

856-
1. We don’t know what contract (ABI) is located at a given address. Should be the case, when the contract either wasn’t uploaded via Seth or we haven’t supplied Seth with a contract map as part of its configuration (more on that later).
856+
There are two possible starting points:
857857

858-
a. We sequentially iterate over all known ABIs (map: `name -> ABI_name`) checking whether it has a method with a given signature. Once we get a first match we will upsert that (`address -> ABI_name`) data into the contract map and return the ABI.
858+
1. We don’t know what contract (ABI) is located at a given address. That is the case, when the contract either wasn’t uploaded via Seth or we haven’t supplied Seth with a contract map as part of its configuration (more on that later).
859859

860-
The caveat here is that if the method we are searching for belongs is present in more than one ABI we might associate the address with an incorrect address (we will use the first match).
860+
a. We sequentially iterate over all known ABIs (map: `name -> ABI_name`) checking whether it has a method with a given signature. Once we get a first match we upsert that (`address -> ABI_name`) data into the contract map and return the ABI.
861861

862-
b. If no match is found we will return an error.
862+
The caveat here is that if the method we are searching for is present in more than one ABI we might associate the address with an incorrect address (we use the first match).
863863

864-
2. We know what ABI is located at a given address. It should be the case, when we have either uploaded the contract via Seth, provided Seth with a contract map or already traced a transaction to that address and found an ABI with matching method signature.
864+
b. If no match is found we return an error.
865865

866-
a. We fetch the corresponding ABIand check if it indeed contains the method we are looking for (as mentioned earlier in some cases it might not be the case).
866+
2. We know what ABI is located at a given address. That is the case, when we have either uploaded the contract via Seth, provided Seth with a contract map or already traced a transaction to that address and found an ABI with matching method signature.
867+
868+
a. We fetch the corresponding ABI and check if it indeed contains the method we are looking for (as mentioned earlier in some cases it might not be the case, because we associated the ABI with address using a different method that it shared with some other contract).
867869

868870
b. If it does, we return the ABI.
869871

870-
c. If it doesn’t we iterate over all known ABIs, in the same way as in 1a. If we find a match we update the (`address -> ABI_name`) association in the contract map and return the ABI.
872+
c. If it doesn’t we iterate again over all known ABIs, in the same way as in `1a`. If we find a match we update the (`address -> ABI_name`) association in the contract map and return the ABI.
871873

872-
It is possible that this will happen multiple times in case we have multiple contracts with multiple identical methods, but given a sufficiently diverse set of methods that were called we should eventually arrive at a fully correct contract map.
874+
It is possible that this will happen multiple times, if we have multiple contracts with multiple identical methods, but given a sufficiently diverse set of methods that were called we should eventually arrive at a fully correct contract map.
873875

874876
d. If no match is found we will return an error.
875877

@@ -879,16 +881,16 @@ In order to be able to decode and trace transactions and calls between smart con
879881

880882
## Contract Map
881883

882-
We support in-memory contract map and a TOML file contract map that keeps the association of (`address -> ABI_name`). The latter map is only used for non-simulated networks. Every time we deploy a contract we save (`address -> ABI_name`) entry in the in-memory map.If the network is not a simulated one we also save it in a file. That file can later be pointed to in Seth configuration and we will load the contract map from it (**currently without validating whether we have all the ABIs mentioned in the file**).
884+
We support in-memory contract map and a TOML file-based one that keeps the association of (`address -> ABI_name`). The latter map is only used for non-simulated networks. Every time we deploy a contract we save (`address -> ABI_name`) entry in the in-memory map. If the network is not a simulated one we also save it in a file. That file can later be pointed to in Seth configuration and we will load the contract map from it (**currently without validating whether we have all the ABIs mentioned in the file**).
883885

884-
When saving contract deployment information we will either generate filename for you (if you didn’t configure Seth to use a particular file) using the pattern of `deployed_contracts_${network_name}_${timestamp}.toml` or use the filename provided in Seth TOML configuration file.
886+
When saving contract deployment information we can either generate filename for you (if you didn’t configure Seth to use a particular file) using the pattern of `deployed_contracts_${network_name}_${timestamp}.toml` or use the filename provided in Seth TOML configuration file.
885887

886-
It has to be noted that the file contract map is currently updated only, when new contracts are deployed. There’s no mechanism for updating it if we found the mapping invalid (which might be the case if you manually created the entry in the file).
888+
It has to be noted that the file-based contract map is currently updated only, when new contracts are deployed. There’s no mechanism for updating it if we found the mapping invalid (which might be the case if you manually created the entry in the file).
887889

888890
## Contract Store
889891

890-
Seth can be used with the contract store, but it would have very limited usage as transaction decoding and tracing cannot work with ABIs. Thus, when you initialise Seth with contract store it is necessary that it can load at least one ABI.
892+
Contract store is a component that stores ABIs and contracts' bytecodes. In theory, Seth can be used without it, but it would have very limited usage as transaction decoding and tracing cannot work without ABIs. Thus in practice, we enforce a non-empty Contract Store durin Seth initialisation.
891893

892894
Another use of Contract Store is simplified contract deployment. For that we also need the contract's bytecode. The contract store can be used to store the bytecode of the contract and then deploy it using the `DeployContractFromContractStore(auth *bind.TransactOpts, name string, backend bind.ContractBackend, params ...interface{})` method. When Seth is intialisied with the contract store and no bytecode files (`*.bin`) are provided, it will log a warning, but initialise successfully nonetheless.
893895

894-
If bytecode file wasn't provided you need to use `DeployContract(auth *bind.TransactOpts, name string, abi abi.ABI, bytecode []byte, backend bind.ContractBackend, params ...interface{})` method, which expects you to provide contract name (best if equal to the name of the ABI file), bytecode and the ABI.
896+
If bytecode file wasn't provided, you need to use `DeployContract(auth *bind.TransactOpts, name string, abi abi.ABI, bytecode []byte, backend bind.ContractBackend, params ...interface{})` method, which expects you to provide contract name (best if equal to the name of the ABI file), bytecode and the ABI.

framework/clclient/client.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,15 +557,20 @@ func (c *ChainlinkClient) UpdateEthKeyMaxGasPriceGWei(keyId string, gWei int) (*
557557
}
558558

559559
// ReadPrimaryETHKey reads updated information about the Chainlink's primary ETH key
560-
func (c *ChainlinkClient) ReadPrimaryETHKey() (*ETHKeyData, error) {
560+
func (c *ChainlinkClient) ReadPrimaryETHKey(chainId string) (*ETHKeyData, error) {
561561
ethKeys, err := c.MustReadETHKeys()
562562
if err != nil {
563563
return nil, err
564564
}
565565
if len(ethKeys.Data) == 0 {
566566
return nil, fmt.Errorf("Error retrieving primary eth key on node %s: No ETH keys present", c.URL())
567567
}
568-
return &ethKeys.Data[0], nil
568+
for _, data := range ethKeys.Data {
569+
if data.Attributes.ChainID == chainId {
570+
return &data, nil
571+
}
572+
}
573+
return nil, fmt.Errorf("error retrieving primary eth key on node %s: No ETH keys present for chain %s", c.URL(), chainId)
569574
}
570575

571576
// ReadETHKeyAtIndex reads updated information about the Chainlink's ETH key at given index

framework/components/simple_node_set/fund.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/ethereum/go-ethereum/core/types"
1111
"github.com/ethereum/go-ethereum/crypto"
1212
"github.com/ethereum/go-ethereum/ethclient"
13+
er "github.com/pkg/errors"
1314
"github.com/smartcontractkit/chainlink-testing-framework/framework"
1415
"github.com/smartcontractkit/chainlink-testing-framework/framework/clclient"
1516
"math/big"
@@ -18,7 +19,7 @@ import (
1819
func SendETH(client *ethclient.Client, privateKeyHex string, toAddress string, amount *big.Float) error {
1920
privateKey, err := crypto.HexToECDSA(privateKeyHex)
2021
if err != nil {
21-
return fmt.Errorf("failed to parse private key: %v", err)
22+
return er.Wrap(err, "failed to parse private key")
2223
}
2324
wei := new(big.Int)
2425
amountWei := new(big.Float).Mul(amount, big.NewFloat(1e18))
@@ -33,29 +34,29 @@ func SendETH(client *ethclient.Client, privateKeyHex string, toAddress string, a
3334

3435
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
3536
if err != nil {
36-
return fmt.Errorf("failed to fetch nonce: %v", err)
37+
return er.Wrap(err, "failed to fetch nonce")
3738
}
3839

3940
gasPrice, err := client.SuggestGasPrice(context.Background())
4041
if err != nil {
41-
return fmt.Errorf("failed to fetch gas price: %v", err)
42+
return er.Wrap(err, "failed to fetch gas price")
4243
}
4344
gasLimit := uint64(21000) // Standard gas limit for ETH transfer
4445

4546
tx := types.NewTransaction(nonce, common.HexToAddress(toAddress), wei, gasLimit, gasPrice, nil)
4647

4748
chainID, err := client.NetworkID(context.Background())
4849
if err != nil {
49-
return fmt.Errorf("failed to fetch chain ID: %v", err)
50+
return er.Wrap(err, "failed to fetch chain ID")
5051
}
5152
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
5253
if err != nil {
53-
return fmt.Errorf("failed to sign transaction: %v", err)
54+
return er.Wrap(err, "failed to sign transaction")
5455
}
5556

5657
err = client.SendTransaction(context.Background(), signedTx)
5758
if err != nil {
58-
return fmt.Errorf("failed to send transaction: %v", err)
59+
return er.Wrap(err, "failed to send transaction")
5960
}
6061
framework.L.Info().Msgf("Transaction sent: %s", signedTx.Hash().Hex())
6162
_, err = bind.WaitMined(context.Background(), client, signedTx)
@@ -67,13 +68,17 @@ func FundNodes(c *ethclient.Client, nodes []*clclient.ChainlinkClient, pkey stri
6768
if ethAmount == 0 {
6869
return errors.New("funds_eth is 0, set some value in config, ex.: funds_eth = 30.0")
6970
}
71+
chainID, err := c.ChainID(context.Background())
72+
if err != nil {
73+
return er.Wrap(err, "failed to fetch chain ID")
74+
}
7075
for _, cl := range nodes {
71-
ek, err := cl.ReadPrimaryETHKey()
76+
ek, err := cl.ReadPrimaryETHKey(chainID.String())
7277
if err != nil {
7378
return err
7479
}
7580
if err := SendETH(c, pkey, ek.Attributes.Address, big.NewFloat(ethAmount)); err != nil {
76-
return fmt.Errorf("failed to fund CL node %s: %w", ek.Attributes.Address, err)
81+
return er.Wrapf(err, "failed to fund CL node %s", ek.Attributes.Address)
7782
}
7883
}
7984
return nil

tools/flakeguard/cmd/aggregate_results.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ var AggregateResultsCmd = &cobra.Command{
121121

122122
// Remove logs from test results for the report without logs
123123
for i := range failedReportWithLogs.Results {
124-
failedReportWithLogs.Results[i].Outputs = nil
124+
failedReportWithLogs.Results[i].PassedOutputs = nil
125+
failedReportWithLogs.Results[i].FailedOutputs = nil
125126
failedReportWithLogs.Results[i].PackageOutputs = nil
126127
}
127128

@@ -137,7 +138,8 @@ var AggregateResultsCmd = &cobra.Command{
137138

138139
// Remove logs from test results for the aggregated report
139140
for i := range aggregatedReport.Results {
140-
aggregatedReport.Results[i].Outputs = nil
141+
aggregatedReport.Results[i].PassedOutputs = nil
142+
aggregatedReport.Results[i].FailedOutputs = nil
141143
aggregatedReport.Results[i].PackageOutputs = nil
142144
}
143145

tools/flakeguard/reports/data.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ type TestResult struct {
3636
Failures int
3737
Successes int
3838
Skips int
39-
Outputs []string
39+
Outputs map[string][]string `json:"-"` // Temporary storage for outputs during test run
40+
PassedOutputs map[string][]string // Outputs for passed runs
41+
FailedOutputs map[string][]string // Outputs for failed runs
4042
Durations []time.Duration
4143
PackageOutputs []string
4244
TestPath string
@@ -191,7 +193,18 @@ func aggregateFromReports(reports ...*TestReport) (*TestReport, error) {
191193
func mergeTestResults(a, b TestResult) TestResult {
192194
a.Runs += b.Runs
193195
a.Durations = append(a.Durations, b.Durations...)
194-
a.Outputs = append(a.Outputs, b.Outputs...)
196+
if a.PassedOutputs == nil {
197+
a.PassedOutputs = make(map[string][]string)
198+
}
199+
if a.FailedOutputs == nil {
200+
a.FailedOutputs = make(map[string][]string)
201+
}
202+
for runID, outputs := range b.PassedOutputs {
203+
a.PassedOutputs[runID] = append(a.PassedOutputs[runID], outputs...)
204+
}
205+
for runID, outputs := range b.FailedOutputs {
206+
a.FailedOutputs[runID] = append(a.FailedOutputs[runID], outputs...)
207+
}
195208
a.PackageOutputs = append(a.PackageOutputs, b.PackageOutputs...)
196209
a.Successes += b.Successes
197210
a.Failures += b.Failures

tools/flakeguard/reports/data_test.go

Lines changed: 43 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -346,11 +346,13 @@ func TestAggregateOutputs(t *testing.T) {
346346
TestRunCount: 1,
347347
Results: []TestResult{
348348
{
349-
TestName: "TestOutput",
350-
TestPackage: "pkg1",
351-
Runs: 1,
352-
Successes: 1,
353-
Outputs: []string{"Output from report1 test run"},
349+
TestName: "TestOutput",
350+
TestPackage: "pkg1",
351+
Runs: 1,
352+
Successes: 1,
353+
PassedOutputs: map[string][]string{
354+
"run1": {"Output from report1 test run"},
355+
},
354356
PackageOutputs: []string{"Package output from report1"},
355357
},
356358
},
@@ -361,11 +363,13 @@ func TestAggregateOutputs(t *testing.T) {
361363
TestRunCount: 1,
362364
Results: []TestResult{
363365
{
364-
TestName: "TestOutput",
365-
TestPackage: "pkg1",
366-
Runs: 1,
367-
Successes: 1,
368-
Outputs: []string{"Output from report2 test run"},
366+
TestName: "TestOutput",
367+
TestPackage: "pkg1",
368+
Runs: 1,
369+
Successes: 1,
370+
PassedOutputs: map[string][]string{
371+
"run2": {"Output from report2 test run"},
372+
},
369373
PackageOutputs: []string{"Package output from report2"},
370374
},
371375
},
@@ -382,18 +386,22 @@ func TestAggregateOutputs(t *testing.T) {
382386

383387
result := aggregatedReport.Results[0]
384388

385-
// Expected outputs
386-
expectedOutputs := []string{
387-
"Output from report1 test run",
388-
"Output from report2 test run",
389+
expectedOutputs := map[string][]string{
390+
"run1": {
391+
"Output from report1 test run",
392+
},
393+
"run2": {
394+
"Output from report2 test run",
395+
},
389396
}
397+
390398
expectedPackageOutputs := []string{
391399
"Package output from report1",
392400
"Package output from report2",
393401
}
394402

395-
if !reflect.DeepEqual(result.Outputs, expectedOutputs) {
396-
t.Errorf("Expected Outputs %v, got %v", expectedOutputs, result.Outputs)
403+
if !reflect.DeepEqual(result.PassedOutputs, expectedOutputs) {
404+
t.Errorf("Expected Outputs %v, got %v", expectedOutputs, result.PassedOutputs)
397405
}
398406

399407
if !reflect.DeepEqual(result.PackageOutputs, expectedPackageOutputs) {
@@ -407,11 +415,13 @@ func TestAggregateIdenticalOutputs(t *testing.T) {
407415
TestRunCount: 1,
408416
Results: []TestResult{
409417
{
410-
TestName: "TestIdenticalOutput",
411-
TestPackage: "pkg1",
412-
Runs: 1,
413-
Successes: 1,
414-
Outputs: []string{"Identical output"},
418+
TestName: "TestIdenticalOutput",
419+
TestPackage: "pkg1",
420+
Runs: 1,
421+
Successes: 1,
422+
PassedOutputs: map[string][]string{
423+
"run1": {"Identical output"},
424+
},
415425
PackageOutputs: []string{"Identical package output"},
416426
},
417427
},
@@ -422,11 +432,13 @@ func TestAggregateIdenticalOutputs(t *testing.T) {
422432
TestRunCount: 1,
423433
Results: []TestResult{
424434
{
425-
TestName: "TestIdenticalOutput",
426-
TestPackage: "pkg1",
427-
Runs: 1,
428-
Successes: 1,
429-
Outputs: []string{"Identical output"},
435+
TestName: "TestIdenticalOutput",
436+
TestPackage: "pkg1",
437+
Runs: 1,
438+
Successes: 1,
439+
PassedOutputs: map[string][]string{
440+
"run1": {"Identical output"},
441+
},
430442
PackageOutputs: []string{"Identical package output"},
431443
},
432444
},
@@ -443,80 +455,24 @@ func TestAggregateIdenticalOutputs(t *testing.T) {
443455

444456
result := aggregatedReport.Results[0]
445457

446-
// Expected outputs
447-
expectedOutputs := []string{
448-
"Identical output",
449-
"Identical output",
458+
expectedOutputs := map[string][]string{
459+
"run1": {"Identical output", "Identical output"},
450460
}
461+
451462
expectedPackageOutputs := []string{
452463
"Identical package output",
453464
"Identical package output",
454465
}
455466

456-
if !reflect.DeepEqual(result.Outputs, expectedOutputs) {
457-
t.Errorf("Expected Outputs %v, got %v", expectedOutputs, result.Outputs)
467+
if !reflect.DeepEqual(result.PassedOutputs, expectedOutputs) {
468+
t.Errorf("Expected Outputs %v, got %v", expectedOutputs, result.PassedOutputs)
458469
}
459470

460471
if !reflect.DeepEqual(result.PackageOutputs, expectedPackageOutputs) {
461472
t.Errorf("Expected PackageOutputs %v, got %v", expectedPackageOutputs, result.PackageOutputs)
462473
}
463474
}
464475

465-
// TestMergeTestResults tests the mergeTestResults function.
466-
func TestMergeTestResults(t *testing.T) {
467-
a := TestResult{
468-
TestName: "TestA",
469-
TestPackage: "pkg1",
470-
Runs: 2,
471-
Successes: 2,
472-
Failures: 0,
473-
Skips: 0,
474-
Durations: []time.Duration{time.Second, time.Second},
475-
Outputs: []string{"Output1", "Output2"},
476-
PackageOutputs: []string{"PkgOutput1"},
477-
Panic: false,
478-
Race: false,
479-
Skipped: false,
480-
}
481-
482-
b := TestResult{
483-
TestName: "TestA",
484-
TestPackage: "pkg1",
485-
Runs: 3,
486-
Successes: 2,
487-
Failures: 1,
488-
Skips: 0,
489-
Durations: []time.Duration{2 * time.Second, 2 * time.Second, 2 * time.Second},
490-
Outputs: []string{"Output3", "Output4", "Output5"},
491-
PackageOutputs: []string{"PkgOutput2"},
492-
Panic: true,
493-
Race: false,
494-
Skipped: false,
495-
}
496-
497-
merged := mergeTestResults(a, b)
498-
499-
expected := TestResult{
500-
TestName: "TestA",
501-
TestPackage: "pkg1",
502-
Runs: 5,
503-
Successes: 4,
504-
Failures: 1,
505-
Skips: 0,
506-
Durations: []time.Duration{time.Second, time.Second, 2 * time.Second, 2 * time.Second, 2 * time.Second},
507-
Outputs: []string{"Output1", "Output2", "Output3", "Output4", "Output5"},
508-
PackageOutputs: []string{"PkgOutput1", "PkgOutput2"},
509-
Panic: true,
510-
Race: false,
511-
Skipped: false,
512-
PassRatio: 0.8,
513-
}
514-
515-
if !reflect.DeepEqual(merged, expected) {
516-
t.Errorf("Expected %+v, got %+v", expected, merged)
517-
}
518-
}
519-
520476
// TestAvgDuration tests the avgDuration function.
521477
func TestAvgDuration(t *testing.T) {
522478
durations := []time.Duration{

0 commit comments

Comments
 (0)