Skip to content

Commit

Permalink
Merge pull request onflow#6225 from onflow/bastian/account-diff
Browse files Browse the repository at this point in the history
[Util] Add support for detecting differing accounts
  • Loading branch information
turbolent authored Aug 2, 2024
2 parents 8c89495 + de2ff4b commit c5b9e46
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 53 deletions.
78 changes: 68 additions & 10 deletions cmd/util/cmd/diff-states/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"

"github.com/dustin/go-humanize/english"
"github.com/onflow/cadence/runtime/common"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
Expand All @@ -29,7 +31,7 @@ var (
flagState2 string
flagStateCommitment1 string
flagStateCommitment2 string
flagRaw bool
flagMode string
flagAlwaysDiffValues bool
flagNWorker int
flagChain string
Expand Down Expand Up @@ -107,11 +109,11 @@ func init() {
)
_ = Cmd.MarkFlagRequired("output-directory")

Cmd.Flags().BoolVar(
&flagRaw,
"raw",
true,
"Raw or value",
Cmd.Flags().StringVar(
&flagMode,
"mode",
"values",
"one of 'values', 'accounts', or 'raw'; to diff values, accounts, or raw bytes. default is 'values'",
)

Cmd.Flags().BoolVar(
Expand All @@ -137,6 +139,20 @@ func init() {
_ = Cmd.MarkFlagRequired("chain")
}

type mode uint8

const (
modeValues mode = iota
modeAccounts
modeRaw
)

var modeByName = map[string]mode{
"values": modeValues,
"accounts": modeAccounts,
"raw": modeRaw,
}

func run(*cobra.Command, []string) {

chainID := flow.ChainID(flagChain)
Expand All @@ -161,6 +177,18 @@ func run(*cobra.Command, []string) {
log.Fatal().Msg("--state-commitment-2 must be provided when --state-2 is provided")
}

mode, ok := modeByName[flagMode]
if !ok {
modeNames := make([]string, 0, len(modeByName))
for name := range modeByName {
modeNames = append(modeNames, fmt.Sprintf("%q", name))
}
log.Fatal().Msgf(
"--mode must be one of %s",
english.OxfordWordSeries(modeNames, "or"),
)
}

rw := reporters.NewReportFileWriterFactoryWithFormat(flagOutputDirectory, log.Logger, reporters.ReportFormatJSONL).
ReportWriter(ReporterName)
defer rw.Close()
Expand Down Expand Up @@ -194,7 +222,7 @@ func run(*cobra.Command, []string) {
}
}

err := diff(registers1, registers2, chainID, rw, flagNWorker)
err := diff(registers1, registers2, chainID, rw, flagNWorker, mode)
if err != nil {
log.Warn().Err(err).Msgf("failed to diff registers")
}
Expand Down Expand Up @@ -292,6 +320,7 @@ func diffAccount(
accountRegisters2 *registers.AccountRegisters,
chainID flow.ChainID,
rw reporters.ReportWriter,
mode mode,
) (err error) {

if accountRegisters1.Count() != accountRegisters2.Count() {
Expand All @@ -313,7 +342,7 @@ func diffAccount(

if !bytes.Equal(value1, value2) {

if flagRaw {
if mode == modeRaw {
rw.Write(rawDiff{
Owner: owner,
Key: key,
Expand All @@ -329,11 +358,21 @@ func diffAccount(
return nil
})
if err != nil {
if flagRaw || !errors.Is(err, accountsDiffer) {
accountsDiffer := errors.Is(err, accountsDiffer)
if !accountsDiffer {
return err
}

diffValues = true
switch mode {
case modeRaw:
// NO-OP
case modeAccounts:
rw.Write(accountDiff{
Owner: owner,
})
case modeValues:
diffValues = true
}
}

if diffValues {
Expand Down Expand Up @@ -364,6 +403,7 @@ func diff(
chainID flow.ChainID,
rw reporters.ReportWriter,
nWorkers int,
mode mode,
) error {
log.Info().Msgf("Diffing %d accounts", registers1.AccountCount())

Expand Down Expand Up @@ -404,6 +444,7 @@ func diff(
accountRegisters2,
chainID,
rw,
mode,
)
if err != nil {
log.Warn().Err(err).Msgf("failed to diff account %x", []byte(owner))
Expand Down Expand Up @@ -457,6 +498,7 @@ func diff(
job.accountRegisters2,
chainID,
rw,
mode,
)

select {
Expand Down Expand Up @@ -561,6 +603,22 @@ func (e rawDiff) MarshalJSON() ([]byte, error) {
})
}

type accountDiff struct {
Owner string
}

var _ json.Marshaler = accountDiff{}

func (e accountDiff) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Kind string `json:"kind"`
Owner string `json:"owner"`
}{
Kind: "account-diff",
Owner: hex.EncodeToString([]byte(e.Owner)),
})
}

type accountMissing struct {
Owner string
State int
Expand Down
149 changes: 107 additions & 42 deletions cmd/util/cmd/diff-states/diff_states_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,21 @@ func TestDiffStates(t *testing.T) {
file2 := filepath.Join(datadir, "second.payloads")

payloads1 := []*ledger.Payload{
// account 1
newPayload(flow.Address{1}, "a", []byte{2}),
newPayload(flow.Address{1}, "b", []byte{3}),
newPayload(flow.Address{2}, "c", []byte{4}),
newPayload(flow.Address{1}, "c", []byte{4}),
// account 2
newPayload(flow.Address{2}, "d", []byte{5}),
}
payloads2 := []*ledger.Payload{
// account 1, different values for key b and c
newPayload(flow.Address{1}, "a", []byte{2}),
newPayload(flow.Address{1}, "b", []byte{5}),
newPayload(flow.Address{1}, "c", []byte{6}),
// account 3, missing in payloads1
newPayload(flow.Address{3}, "d", []byte{6}),
// account 4, missing in payloads1
newPayload(flow.Address{4}, "e", []byte{7}),
}

Expand All @@ -53,54 +60,112 @@ func TestDiffStates(t *testing.T) {
require.NoError(t, err)
require.Equal(t, len(payloads2), numOfPayloadWritten)

Cmd.SetArgs([]string{
"--payloads-1", file1,
"--payloads-2", file2,
"--chain", string(flow.Emulator),
"--output-directory", datadir,
"--raw",
})
test := func(t *testing.T, mode string) []string {

err = Cmd.Execute()
require.NoError(t, err)
Cmd.SetArgs([]string{
"--payloads-1", file1,
"--payloads-2", file2,
"--chain", string(flow.Emulator),
"--output-directory", datadir,
"--mode", mode,
})

var reportPath string
err = filepath.Walk(
datadir,
func(path string, info fs.FileInfo, err error) error {
if path != datadir && info.IsDir() {
return filepath.SkipDir
}
if strings.HasPrefix(filepath.Base(path), ReporterName) {
reportPath = path
return filepath.SkipAll
}
return err
},
)
require.NoError(t, err)
require.NotEmpty(t, reportPath)
err = Cmd.Execute()
require.NoError(t, err)

report, err := os.Open(reportPath)
require.NoError(t, err)
var reportPath string
err = filepath.Walk(
datadir,
func(path string, info fs.FileInfo, err error) error {
if path != datadir && info.IsDir() {
return filepath.SkipDir
}
if strings.HasPrefix(filepath.Base(path), ReporterName) {
reportPath = path
return filepath.SkipAll
}
return err
},
)
require.NoError(t, err)
require.NotEmpty(t, reportPath)

var msgs [][]byte
decoder := json.NewDecoder(report)
for {
var msg json.RawMessage
err = decoder.Decode(&msg)
if err == io.EOF {
break
}
report, err := os.Open(reportPath)
require.NoError(t, err)

msgs = append(msgs, msg)
var msgs []string
decoder := json.NewDecoder(report)
for {
var msg json.RawMessage
err = decoder.Decode(&msg)
if err == io.EOF {
break
}
require.NoError(t, err)

msgs = append(msgs, string(msg))
}

return msgs
}

assert.Equal(t, 4, len(msgs))
assert.Containsf(t, msgs, []byte(`{"kind":"account-missing","owner":"0200000000000000","state":2}`), "diff report contains account-missing for 0200000000000000")
assert.Containsf(t, msgs, []byte(`{"kind":"account-missing","owner":"0300000000000000","state":1}`), "diff report contains account-missing for 0300000000000000")
assert.Containsf(t, msgs, []byte(`{"kind":"account-missing","owner":"0400000000000000","state":1}`), "diff report contains account-missing for 0400000000000000")
assert.Containsf(t, msgs, []byte(`{"kind":"raw-diff","owner":"0100000000000000","key":"62","value1":"03","value2":"05"}`), "diff report contains raw-diff")
t.Run("raw", func(t *testing.T) {

msgs := test(t, "raw")

assert.Equal(t, 5, len(msgs))
assert.Containsf(t,
msgs,
`{"kind":"account-missing","owner":"0200000000000000","state":2}`,
"diff report contains account-missing for 0200000000000000",
)
assert.Containsf(t,
msgs,
`{"kind":"account-missing","owner":"0300000000000000","state":1}`,
"diff report contains account-missing for 0300000000000000",
)
assert.Containsf(t,
msgs,
`{"kind":"account-missing","owner":"0400000000000000","state":1}`,
"diff report contains account-missing for 0400000000000000",
)
assert.Containsf(t,
msgs,
`{"kind":"raw-diff","owner":"0100000000000000","key":"62","value1":"03","value2":"05"}`,
"diff report contains raw-diff",
)
assert.Containsf(t,
msgs,
`{"kind":"raw-diff","owner":"0100000000000000","key":"63","value1":"04","value2":"06"}`,
"diff report contains raw-diff",
)
})

t.Run("accounts", func(t *testing.T) {

msgs := test(t, "accounts")

assert.Equal(t, 4, len(msgs))
assert.Containsf(t,
msgs,
`{"kind":"account-missing","owner":"0200000000000000","state":2}`,
"diff report contains account-missing for 0200000000000000",
)
assert.Containsf(t,
msgs,
`{"kind":"account-missing","owner":"0300000000000000","state":1}`,
"diff report contains account-missing for 0300000000000000",
)
assert.Containsf(t,
msgs,
`{"kind":"account-missing","owner":"0400000000000000","state":1}`,
"diff report contains account-missing for 0400000000000000",
)
assert.Containsf(t,
msgs,
`{"kind":"account-diff","owner":"0100000000000000"}`,
"diff report contains account-diff",
)
})
})
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ require (
github.com/cockroachdb/pebble v1.1.1
github.com/coreos/go-semver v0.3.0
github.com/docker/go-units v0.5.0
github.com/dustin/go-humanize v1.0.1
github.com/glebarez/go-sqlite v1.22.0
github.com/go-playground/validator/v10 v10.14.1
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb
Expand Down Expand Up @@ -165,7 +166,6 @@ require (
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 // indirect
Expand Down

0 comments on commit c5b9e46

Please sign in to comment.