scripts: add compare-states, related to #2337

Allows to quickly find the bad block and compare application
logs. Theoretically could also walk through the MPT to find the difference
there, but it's not needed now.
This commit is contained in:
Roman Khimov 2022-01-25 21:12:07 +03:00
parent b011af4723
commit d1a9aa1d0d
3 changed files with 148 additions and 0 deletions

2
go.mod
View file

@ -4,6 +4,7 @@ require (
github.com/abiosoft/ishell/v2 v2.0.2 github.com/abiosoft/ishell/v2 v2.0.2
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db
github.com/btcsuite/btcd v0.22.0-beta github.com/btcsuite/btcd v0.22.0-beta
github.com/davecgh/go-spew v1.1.1
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/golang-lru v0.5.4
github.com/holiman/uint256 v1.2.0 github.com/holiman/uint256 v1.2.0
@ -13,6 +14,7 @@ require (
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659 github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659
github.com/nspcc-dev/rfc6979 v0.2.0 github.com/nspcc-dev/rfc6979 v0.2.0
github.com/pierrec/lz4 v2.6.1+incompatible github.com/pierrec/lz4 v2.6.1+incompatible
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_golang v1.11.0
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954

View file

@ -0,0 +1,146 @@
package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/davecgh/go-spew/spew"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/pmezard/go-difflib/difflib"
"github.com/urfave/cli"
)
func initClient(addr string, name string) (*client.Client, uint32, error) {
c, err := client.New(context.Background(), addr, client.Options{})
if err != nil {
return nil, 0, fmt.Errorf("RPC %s: %w", name, err)
}
err = c.Init()
if err != nil {
return nil, 0, fmt.Errorf("RPC %s init: %w", name, err)
}
h, err := c.GetBlockCount()
if err != nil {
return nil, 0, fmt.Errorf("RPC %s block count: %w", name, err)
}
return c, h, nil
}
func getRoots(ca *client.Client, cb *client.Client, h uint32) (util.Uint256, util.Uint256, error) {
ra, err := ca.GetStateRootByHeight(h)
if err != nil {
return util.Uint256{}, util.Uint256{}, fmt.Errorf("getstateroot from A for %d: %w", h, err)
}
rb, err := cb.GetStateRootByHeight(h)
if err != nil {
return util.Uint256{}, util.Uint256{}, fmt.Errorf("getstateroot from B for %d: %w", h, err)
}
return ra.Root, rb.Root, nil
}
func bisectState(ca *client.Client, cb *client.Client, h uint32) (uint32, error) {
ra, rb, err := getRoots(ca, cb, 0)
if err != nil {
return 0, err
}
fmt.Printf("at %d: %s vs %s\n", 0, ra.StringLE(), rb.StringLE())
if ra != rb {
return 0, nil
}
good := uint32(0)
ra, rb, err = getRoots(ca, cb, h)
if err != nil {
return 0, err
}
fmt.Printf("at %d: %s vs %s\n", h, ra.StringLE(), rb.StringLE())
if ra.Equals(rb) {
return 0, fmt.Errorf("state matches at %d", h)
}
bad := h
for bad-good > 1 {
next := good + (bad-good)/2
ra, rb, err = getRoots(ca, cb, next)
if err != nil {
return 0, err
}
fmt.Printf("at %d: %s vs %s\n", next, ra.StringLE(), rb.StringLE())
if ra == rb {
good = next
} else {
bad = next
}
}
return bad, nil
}
func cliMain(c *cli.Context) error {
a := c.Args().Get(0)
b := c.Args().Get(1)
if a == "" {
return errors.New("no arguments given")
}
if b == "" {
return errors.New("missing second argument")
}
ca, ha, err := initClient(a, "A")
if err != nil {
return err
}
cb, hb, err := initClient(b, "B")
if err != nil {
return err
}
if ha != hb {
return errors.New("chains have different heights")
}
h, err := bisectState(ca, cb, ha-1)
if err != nil {
return err
}
blk, err := ca.GetBlockByIndex(h)
if err != nil {
return err
}
fmt.Printf("state differs at %d, block %s\n", h, blk.Hash().StringLE())
for _, t := range blk.Transactions {
fmt.Printf("transaction %s:\n", t.Hash().StringLE())
la, err := ca.GetApplicationLog(t.Hash(), nil)
if err != nil {
return err
}
lb, err := cb.GetApplicationLog(t.Hash(), nil)
if err != nil {
return err
}
da := spew.Sdump(la)
db := spew.Sdump(lb)
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
A: difflib.SplitLines(da),
B: difflib.SplitLines(db),
FromFile: a,
FromDate: "",
ToFile: b,
ToDate: "",
Context: 1,
})
fmt.Println(diff)
}
return nil
}
func main() {
ctl := cli.NewApp()
ctl.Name = "compare-states"
ctl.Version = "1.0"
ctl.Usage = "compare-states RPC_A RPC_B"
ctl.Action = cliMain
if err := ctl.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, ctl.Usage)
os.Exit(1)
}
}