forked from TrueCloudLab/neoneo-go
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:
parent
b011af4723
commit
d1a9aa1d0d
3 changed files with 148 additions and 0 deletions
2
go.mod
2
go.mod
|
@ -4,6 +4,7 @@ require (
|
|||
github.com/abiosoft/ishell/v2 v2.0.2
|
||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db
|
||||
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/hashicorp/golang-lru v0.5.4
|
||||
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/rfc6979 v0.2.0
|
||||
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/stretchr/testify v1.7.0
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954
|
||||
|
|
146
scripts/compare-states/compare-states.go
Normal file
146
scripts/compare-states/compare-states.go
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue