Compare commits
No commits in common. "master" and "empty" have entirely different histories.
14 changed files with 2 additions and 2251 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
bin/*
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 TrueCloudLab
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
4
Makefile
4
Makefile
|
@ -1,4 +0,0 @@
|
|||
.PHONY: build
|
||||
|
||||
build:
|
||||
CGO_ENABLED=0 go build -o ./bin/monza ./
|
168
README.md
168
README.md
|
@ -1,167 +1,3 @@
|
|||
# Monza
|
||||
# WIP area: this repo is just a fork!
|
||||
|
||||
Find notifications in [Neo3](https://neo.org/) compatible chains and more.
|
||||
|
||||
## Features
|
||||
|
||||
- Stores chain blocks in the filesystem, so it will not fetch it again
|
||||
at restart
|
||||
- Works with multiple chains by storing chain blocks based on magic number
|
||||
- Detailed notification output for NEP and FrostFS notifications
|
||||
- Use relative numbers for search interval
|
||||
- Use nice names for native contracts
|
||||
- Search multiple notifications at once
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
$ monza run -r http://localhost:30333 --from 22 --to 24 -n NewEpoch:* -n Transfer:gas
|
||||
syncing 100% [##################################################] (2/2, 3686 blocks/s)
|
||||
block:22 at:2023-09-27T13:50:03+03:00 name:Transfer from:56c989e76f9a2ca05bb5caa6c96f524d905accd8 to:nil amount:69915670
|
||||
block:22 at:2023-09-27T13:50:03+03:00 name:Transfer from:nil to:b248508f4ef7088e10c48f14d04be3272ca29eee amount:1219580
|
||||
block:22 at:2023-09-27T13:50:03+03:00 name:Transfer from:nil to:b248508f4ef7088e10c48f14d04be3272ca29eee amount:50000000
|
||||
block:22 at:2023-09-27T13:50:03+03:00 name:NewEpoch epoch:1
|
||||
block:23 at:2023-09-27T13:50:04+03:00 name:Transfer from:05195d17c8f013e258eb8dde1236c19d9a61b608 to:nil amount:33917200
|
||||
block:23 at:2023-09-27T13:50:04+03:00 name:Transfer from:6a131b2be19b7618dc22dbc8147015d947af67ce to:nil amount:12321080
|
||||
block:23 at:2023-09-27T13:50:04+03:00 name:Transfer from:b248508f4ef7088e10c48f14d04be3272ca29eee to:nil amount:12325080
|
||||
block:23 at:2023-09-27T13:50:04+03:00 name:Transfer from:nil to:b248508f4ef7088e10c48f14d04be3272ca29eee amount:8917170
|
||||
block:23 at:2023-09-27T13:50:04+03:00 name:Transfer from:nil to:b248508f4ef7088e10c48f14d04be3272ca29eee amount:20000000
|
||||
block:23 at:2023-09-27T13:50:04+03:00 name:Transfer from:nil to:b248508f4ef7088e10c48f14d04be3272ca29eee amount:50000000
|
||||
block:23 at:2023-09-27T13:50:04+03:00 name:Transfer from:6a131b2be19b7618dc22dbc8147015d947af67ce to:c1e14f19c3e60d0b9244d06dd7ba9b113135ec3b amount:243839460
|
||||
block:23 at:2023-09-27T13:50:04+03:00 name:Transfer from:b248508f4ef7088e10c48f14d04be3272ca29eee to:c1e14f19c3e60d0b9244d06dd7ba9b113135ec3b amount:250146763165
|
||||
```
|
||||
|
||||
### Detailed output
|
||||
|
||||
- NEP-17 `Transfer`
|
||||
|
||||
```
|
||||
block:23 at:2023-09-27T13:50:04+03:00 name:Transfer from:6a131b2be19b7618dc22dbc8147015d947af67ce to:c1e14f19c3e60d0b9244d06dd7ba9b113135ec3b amount:243839460
|
||||
```
|
||||
|
||||
- FrostFS `NewEpoch`
|
||||
|
||||
```
|
||||
block:22 at:2023-09-27T13:50:03+03:00 name:NewEpoch epoch:1
|
||||
```
|
||||
|
||||
- FrostFS `AddPeerSuccess`
|
||||
|
||||
```
|
||||
block:21 at:2023-09-27T13:50:02+03:00 name:AddPeerSuccess pubkey:[..6a8131]
|
||||
```
|
||||
|
||||
### Notifications
|
||||
|
||||
Search notifications based on notification name and contract address.
|
||||
|
||||
```
|
||||
monza run -r [endpoint] --from 110000 --to 110100 -n NewEpoch:ab8a83432af3cd32ce6ba3797f62b1ba330d7c3d
|
||||
```
|
||||
|
||||
Use wildcard to search notifications from any contract.
|
||||
|
||||
```
|
||||
monza run -r [endpoint] --from 110000 --to 110100 -n NewEpoch:*
|
||||
```
|
||||
|
||||
Use native contract names such as `gas` and `neo`.
|
||||
|
||||
```
|
||||
monza run -r [endpoint] --from 110000 --to 110100 -n Transfer:gas
|
||||
```
|
||||
|
||||
Search for multiple notifications.
|
||||
|
||||
```
|
||||
monza run -r [endpoint] --from 110000 --to 110100 -n Transfer:gas -n NewEpoch:*
|
||||
```
|
||||
|
||||
### Intervals
|
||||
|
||||
Define start and stop blocks.
|
||||
|
||||
```
|
||||
monza run -r [endpoint] --from 110000 --to 110100 -n NewEpoch:*
|
||||
```
|
||||
|
||||
Omit `--to` flag to search up to the latest block.
|
||||
|
||||
```
|
||||
monza run -r [endpoint] --from 110000 -n NewEpoch:*
|
||||
```
|
||||
|
||||
To look for `100` blocks before the latest block use prefix `m` (minus)
|
||||
|
||||
```
|
||||
monza run -r [endpoint] --from m100 -n NewEpoch:*
|
||||
```
|
||||
|
||||
To look for `100` blocks after specified `from` block, use prefix `p` (plus)
|
||||
|
||||
```
|
||||
monza run -r [endpoint] --from 101230 --to p100 -n NewEpoch:*
|
||||
```
|
||||
|
||||
### Other
|
||||
|
||||
Blocks are stored in bolt databases. Specify database dir with `-c` flag
|
||||
(default path is `$HOME/.config/monza`)
|
||||
|
||||
```
|
||||
monza run -r [endpoint] -c ./cache --from 110000 --to 110100 -n NewEpoch:*
|
||||
```
|
||||
|
||||
To speed up block fetching from the RPC node, use more parallel workers with
|
||||
`-w` flag.
|
||||
|
||||
```
|
||||
monza run -r [endpoint] -w 100 --from 110000 --to 110100 -n NewEpoch:*
|
||||
```
|
||||
|
||||
To disable progress bar use `--disable-progress-bar` flag.
|
||||
|
||||
### Stutter
|
||||
|
||||
Stutter command searches for blocks that produced with threshold a delay
|
||||
or slower.
|
||||
|
||||
```
|
||||
$ monza stutter -r [endpoint] --from 1159200 --to p30 --threshold 20s
|
||||
syncing 100% [##################################################] (30/30, 10 blocks/s)
|
||||
block:1159201 at:2022-03-29T18:20:42+03:00
|
||||
block:1159202 at:2022-03-29T18:22:12+03:00 [<- stutter for 1m30s]
|
||||
-- skipped 5 blocks --
|
||||
block:1159208 at:2022-03-29T18:23:43+03:00
|
||||
block:1159209 at:2022-03-29T18:25:13+03:00 [<- stutter for 1m30s]
|
||||
-- skipped 5 blocks --
|
||||
block:1159215 at:2022-03-29T18:26:43+03:00
|
||||
block:1159216 at:2022-03-29T18:28:13+03:00 [<- stutter for 1m30s]
|
||||
-- skipped 5 blocks --
|
||||
block:1159222 at:2022-03-29T18:29:43+03:00
|
||||
block:1159223 at:2022-03-29T18:31:13+03:00 [<- stutter for 1m30s]
|
||||
```
|
||||
|
||||
### Explorer
|
||||
|
||||
Run monza in interactive mode to navigate through blocks, transactions and
|
||||
notifications with `explore` command.
|
||||
|
||||
```
|
||||
$ monza explore -r [endpoint]
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
Use `make build` command. Binary will be stored in `./bin/monza`.
|
||||
|
||||
## To Do
|
||||
|
||||
- [ ] `monza cache` command to manage bbolt instances: provide size and option to delete
|
||||
- [ ] Add verbose flag with for detailed view of notification body
|
||||
- [ ] Add more native contract hashes aliases
|
||||
- [ ] More NEP support (NEP-11?)
|
||||
|
||||
## License
|
||||
|
||||
Source code is available under the [MIT License](/LICENSE).
|
||||
Useful things may be published only in [other branches](../../../branches)
|
||||
|
|
194
encoding.go
194
encoding.go
|
@ -1,194 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
const nonCompatibleMsg = "not compatible with FrostFS"
|
||||
|
||||
func PrintEvent(b *block.Block, n state.NotificationEvent, extra string) {
|
||||
d := time.Unix(int64(b.Timestamp/1e3), 0)
|
||||
s := fmt.Sprintf("block:%d at:%s name:%s",
|
||||
b.Index, d.Format(time.RFC3339), n.Name,
|
||||
)
|
||||
|
||||
if len(extra) != 0 {
|
||||
s += fmt.Sprintf(" [%s]", extra)
|
||||
}
|
||||
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
func PrintTransfer(b *block.Block, n state.NotificationEvent) {
|
||||
const nonCompatibleMsg = "not NEP-17 compatible"
|
||||
|
||||
items, ok := n.Item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) != 3 {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
snd, err := items[0].TryBytes()
|
||||
if err != nil {
|
||||
snd = nil
|
||||
}
|
||||
|
||||
rcv, err := items[1].TryBytes()
|
||||
if err != nil {
|
||||
rcv = nil
|
||||
}
|
||||
|
||||
bigAmount, err := items[2].TryInteger()
|
||||
if err != nil {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
var sndStr, rcvStr = "nil", "nil"
|
||||
if snd != nil {
|
||||
sndStr = hex.EncodeToString(revertBytes(snd))
|
||||
}
|
||||
|
||||
if rcv != nil {
|
||||
rcvStr = hex.EncodeToString(revertBytes(rcv))
|
||||
}
|
||||
|
||||
d := time.Unix(int64(b.Timestamp/1e3), 0)
|
||||
|
||||
s := fmt.Sprintf("block:%d at:%s name:%s from:%s to:%s amount:%d",
|
||||
b.Index, d.Format(time.RFC3339), n.Name,
|
||||
sndStr, rcvStr, bigAmount.Int64(),
|
||||
)
|
||||
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
func PrintNewEpoch(b *block.Block, n state.NotificationEvent) {
|
||||
items, ok := n.Item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) != 1 {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
epoch, err := items[0].TryInteger()
|
||||
if err != nil {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
d := time.Unix(int64(b.Timestamp/1e3), 0)
|
||||
|
||||
s := fmt.Sprintf("block:%d at:%s name:%s epoch:%d",
|
||||
b.Index, d.Format(time.RFC3339), n.Name, epoch,
|
||||
)
|
||||
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
func PrintAddPeerSuccess(b *block.Block, n state.NotificationEvent) {
|
||||
items, ok := n.Item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) != 1 {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := items[0].TryBytes()
|
||||
if err != nil {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
d := time.Unix(int64(b.Timestamp/1e3), 0)
|
||||
|
||||
s := fmt.Sprintf("block:%d at:%s name:%s pubkey:[..%s]",
|
||||
b.Index, d.Format(time.RFC3339), n.Name,
|
||||
hex.EncodeToString(data[len(data)-3:]),
|
||||
)
|
||||
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
func PrintUpdateState(b *block.Block, n state.NotificationEvent) {
|
||||
items, ok := n.Item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) != 2 {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
st, err := items[0].TryInteger()
|
||||
if err != nil {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
var stateStr string
|
||||
switch v := st.Uint64(); v {
|
||||
case 1:
|
||||
stateStr = "online"
|
||||
case 2:
|
||||
stateStr = "offline"
|
||||
default:
|
||||
stateStr = fmt.Sprintf("%d(unknown)", v)
|
||||
}
|
||||
|
||||
pubkey, err := items[1].TryBytes()
|
||||
if err != nil {
|
||||
PrintEvent(b, n, nonCompatibleMsg)
|
||||
return
|
||||
}
|
||||
|
||||
d := time.Unix(int64(b.Timestamp/1e3), 0)
|
||||
|
||||
s := fmt.Sprintf("block:%d at:%s name:%s pubkey:[..%s] state:%s",
|
||||
b.Index, d.Format(time.RFC3339), n.Name,
|
||||
hex.EncodeToString(pubkey[len(pubkey)-3:]),
|
||||
stateStr,
|
||||
)
|
||||
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
func PrintBlock(b *block.Block, extra string) {
|
||||
d := time.Unix(int64(b.Timestamp/1e3), 0)
|
||||
s := fmt.Sprintf("block:%d at:%s", b.Index, d.Format(time.RFC3339))
|
||||
|
||||
if len(extra) != 0 {
|
||||
s += fmt.Sprintf(" [%s]", extra)
|
||||
}
|
||||
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
func revertBytes(data []byte) []byte {
|
||||
ln := len(data)
|
||||
for i := 0; i < ln/2; i++ {
|
||||
data[i], data[ln-1-i] = data[ln-1-i], data[i]
|
||||
}
|
||||
return data
|
||||
}
|
521
explorer.go
521
explorer.go
|
@ -1,521 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/monza/internal/bytecode"
|
||||
"git.frostfs.info/TrueCloudLab/monza/internal/chain"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
Explorer struct {
|
||||
ctx context.Context
|
||||
chain *chain.Chain
|
||||
endpoint string
|
||||
app *tview.Application
|
||||
|
||||
jobCh chan fetchTask
|
||||
errCh chan error
|
||||
wg sync.WaitGroup
|
||||
|
||||
searchErrFlag bool
|
||||
}
|
||||
|
||||
fetchTask struct {
|
||||
txHash *util.Uint256
|
||||
index uint32
|
||||
}
|
||||
)
|
||||
|
||||
const defaultExploreWorkers = 100
|
||||
|
||||
func explorer(c *cli.Context) (err error) {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
|
||||
// parse blockchain info
|
||||
cacheDir := c.String(cacheFlagKey)
|
||||
if len(cacheDir) == 0 {
|
||||
cacheDir, err = defaultConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
endpoint := c.String(endpointFlagKey)
|
||||
blockchain, err := chain.Open(ctx, cacheDir, endpoint, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot initialize remote blockchain client: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
blockchain.Close()
|
||||
cancel()
|
||||
}()
|
||||
|
||||
e := Explorer{
|
||||
ctx: ctx,
|
||||
chain: blockchain,
|
||||
endpoint: endpoint,
|
||||
app: tview.NewApplication(),
|
||||
jobCh: make(chan fetchTask),
|
||||
errCh: make(chan error),
|
||||
}
|
||||
e.startWorkers(defaultExploreWorkers)
|
||||
return e.Run()
|
||||
}
|
||||
|
||||
func (e *Explorer) Run() error {
|
||||
// UI basic elements
|
||||
blockList := tview.NewList()
|
||||
searchInput := tview.NewInputField()
|
||||
blockInfo := tview.NewTextView()
|
||||
txCounter := tview.NewTextView()
|
||||
txList := tview.NewList()
|
||||
notifications := tview.NewTextView()
|
||||
|
||||
// UI containers
|
||||
blockFlex := tview.NewFlex().
|
||||
SetDirection(tview.FlexRow).
|
||||
AddItem(blockInfo, 0, 1, false).
|
||||
AddItem(txCounter, 2, 2, false).
|
||||
AddItem(txList, 5, 3, false)
|
||||
appGrid := tview.NewGrid().
|
||||
SetRows(15, -1, 1).
|
||||
SetColumns(-1, -2).
|
||||
AddItem(blockList, 0, 0, 2, 1, 0, 0, false).
|
||||
AddItem(searchInput, 2, 0, 1, 2, 0, 0, false).
|
||||
AddItem(blockFlex, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(notifications, 1, 1, 1, 1, 0, 0, false)
|
||||
|
||||
// Initialize element style
|
||||
blockList.ShowSecondaryText(false).SetWrapAround(false)
|
||||
blockList.SetBorder(true).SetTitle("Blocks")
|
||||
searchInput.SetFieldBackgroundColor(tcell.ColorBlack)
|
||||
txList.ShowSecondaryText(false)
|
||||
setFocusColorStyle(blockList.Box, blockList.Box)
|
||||
setFocusColorStyle(blockFlex.Box, txList.Box)
|
||||
setFocusColorStyle(notifications.Box, notifications.Box)
|
||||
|
||||
// Handle redrawing events
|
||||
blockList.SetDrawFunc(func(_ tcell.Screen, _, _, _, _ int) (int, int, int, int) {
|
||||
posX, posY, width, height := blockList.GetInnerRect()
|
||||
e.redrawBlockList(blockList, height)
|
||||
return posX, posY, width, height
|
||||
})
|
||||
|
||||
// Handle non-default keyboard events
|
||||
appGrid.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Rune() == '/' {
|
||||
e.searchStatusBar(searchInput)
|
||||
e.app.SetFocus(searchInput)
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
blockList.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Rune() {
|
||||
case 'q':
|
||||
e.app.Stop()
|
||||
return nil
|
||||
case 'r':
|
||||
e.refillBlockList(blockList)
|
||||
return nil
|
||||
default:
|
||||
return event
|
||||
}
|
||||
})
|
||||
searchInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if !e.searchErrFlag {
|
||||
return event
|
||||
}
|
||||
e.searchErrFlag = false
|
||||
e.defaultStatusBar(searchInput, blockList.GetItemCount())
|
||||
e.app.SetFocus(blockList)
|
||||
return nil
|
||||
})
|
||||
txList.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyTAB || event.Key() == tcell.KeyEnter {
|
||||
e.app.SetFocus(notifications)
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'q' {
|
||||
e.hideBlockFlex(blockFlex, blockInfo, txCounter, notifications, txList)
|
||||
e.app.SetFocus(blockList)
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
notifications.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyTAB || event.Rune() == 'q' {
|
||||
e.app.SetFocus(txList)
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
// Handle search input logic
|
||||
searchInput.SetDoneFunc(func(key tcell.Key) {
|
||||
e.hideBlockFlex(blockFlex, blockInfo, txCounter, notifications, txList)
|
||||
e.refillBlockList(blockList)
|
||||
|
||||
input := searchInput.GetText()
|
||||
blockCount := blockList.GetItemCount()
|
||||
|
||||
// Try parse as lookingBlock index
|
||||
lookingBlock, err := strconv.Atoi(input)
|
||||
if err == nil {
|
||||
if lookingBlock < 0 || lookingBlock >= blockCount {
|
||||
e.errorStatusBar(searchInput, "invalid block number")
|
||||
return
|
||||
}
|
||||
from, to := blockIndexRange(lookingBlock, blockCount, 50)
|
||||
e.cacheBlocks(from, to)
|
||||
e.defaultStatusBar(searchInput, blockCount)
|
||||
blockList.SetCurrentItem(-lookingBlock - 1)
|
||||
e.app.SetFocus(blockList)
|
||||
return
|
||||
}
|
||||
// Try parse as transaction hash
|
||||
h, err := util.Uint256DecodeStringLE(input)
|
||||
if err == nil {
|
||||
appLog, err := e.chain.Client.GetApplicationLog(h, nil)
|
||||
if err != nil {
|
||||
e.errorStatusBar(searchInput, "tx hash not found")
|
||||
return
|
||||
}
|
||||
chainBlock, err := e.chain.BlockByHash(appLog.Container)
|
||||
if err != nil {
|
||||
e.errorStatusBar(searchInput, "can't get block of specified tx")
|
||||
return
|
||||
}
|
||||
from, to := blockIndexRange(int(chainBlock.Index), blockCount, 50)
|
||||
e.cacheBlocks(from, to)
|
||||
e.defaultStatusBar(searchInput, blockCount)
|
||||
blockList.SetCurrentItem(-int(chainBlock.Index) - 1)
|
||||
e.app.SetFocus(blockList)
|
||||
return
|
||||
}
|
||||
e.errorStatusBar(searchInput, "invalid input, expect valid block number or tx hash")
|
||||
})
|
||||
|
||||
// Handle selecting block in block list
|
||||
blockList.SetSelectedFunc(func(i int, s1, s2 string, r rune) {
|
||||
chainBlock, err := e.chain.Block(uint32(blockList.GetItemCount() - i - 1))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e.cacheNotifications(chainBlock)
|
||||
e.displayBlockFlex(blockFlex, blockInfo, txCounter, notifications, txList, chainBlock)
|
||||
e.app.SetFocus(txList)
|
||||
})
|
||||
|
||||
// Handle select transaction in block flex
|
||||
txList.SetChangedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
|
||||
txCounter.SetText(fmt.Sprintf("Transaction %d of %d\n---", index+1, txList.GetItemCount()))
|
||||
txHash, err := util.Uint256DecodeStringLE(mainText)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
appLog, err := e.chain.ApplicationLog(txHash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
events := make([]state.NotificationEvent, 0)
|
||||
for _, execution := range appLog.Executions {
|
||||
events = append(events, execution.Events...)
|
||||
}
|
||||
|
||||
var res string
|
||||
for _, event := range events {
|
||||
v, err := formatNotification(event)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
res += v
|
||||
}
|
||||
|
||||
res += fmt.Sprintf("\nContract calls:\n")
|
||||
calls, err := e.chain.Calls(txHash)
|
||||
for _, call := range calls {
|
||||
res += formatCall(call)
|
||||
}
|
||||
|
||||
notifications.SetText(res)
|
||||
notifications.ScrollToBeginning()
|
||||
})
|
||||
|
||||
// Initialize element data
|
||||
e.fillBlockList(blockList)
|
||||
e.defaultStatusBar(searchInput, blockList.GetItemCount())
|
||||
|
||||
// Start UI
|
||||
e.app.SetRoot(appGrid, true).SetFocus(blockList)
|
||||
return e.app.Run()
|
||||
}
|
||||
|
||||
func (e *Explorer) fillBlockList(list *tview.List) {
|
||||
to, err := e.chain.Client.GetBlockCount()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// adding is much faster than inserting
|
||||
// so we reverse the order to use that
|
||||
for i := int64(to) - 1; i >= 0; i-- {
|
||||
line := fmt.Sprintf("#%d", i)
|
||||
list.AddItem(line, "", 0, nil)
|
||||
}
|
||||
|
||||
// cache some blocks upfront for smoother scrolling
|
||||
var from uint32
|
||||
if to > 100 {
|
||||
from = to - 100
|
||||
}
|
||||
e.cacheBlocks(from, to-1)
|
||||
}
|
||||
|
||||
func (e *Explorer) refillBlockList(list *tview.List) {
|
||||
from := uint32(list.GetItemCount())
|
||||
|
||||
to, err := e.chain.Client.GetBlockCount()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i := from; i < to; i++ {
|
||||
line := fmt.Sprintf("#%d", i)
|
||||
list.InsertItem(0, line, "", 0, nil)
|
||||
}
|
||||
|
||||
e.cacheBlocks(from, to-1)
|
||||
}
|
||||
|
||||
func (e *Explorer) redrawBlockList(list *tview.List, height int) {
|
||||
currentItemIndex := list.GetCurrentItem()
|
||||
itemCount := list.GetItemCount()
|
||||
|
||||
// range of blocks for detailed info in listbox
|
||||
// to avoid whole blockchain fetching, app works
|
||||
// with small ranges of visible blocks on the screen
|
||||
fromIndex := currentItemIndex - height
|
||||
if fromIndex < 0 {
|
||||
fromIndex = 0
|
||||
}
|
||||
|
||||
toIndex := currentItemIndex + height
|
||||
if toIndex > itemCount {
|
||||
toIndex = itemCount
|
||||
}
|
||||
|
||||
e.cacheBlocks(toBlockIndex(toIndex, itemCount), toBlockIndex(fromIndex, itemCount))
|
||||
for i := fromIndex; i < toIndex; i++ {
|
||||
elem, _ := list.GetItemText(i)
|
||||
// ignore blocks that has been parsed
|
||||
if strings.Contains(elem, "tx") {
|
||||
continue
|
||||
}
|
||||
blockIndex := toBlockIndex(i, itemCount)
|
||||
chainBlock, err := e.chain.Block(blockIndex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ts := time.Unix(int64(chainBlock.Timestamp/1e3), 0)
|
||||
richText := fmt.Sprintf("#%d [%s] txs:%d", blockIndex, ts.Format(time.RFC3339), len(chainBlock.Transactions))
|
||||
list.SetItemText(i, richText, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Explorer) startWorkers(amount int) {
|
||||
worker := func(ctx context.Context, ch <-chan fetchTask, out chan<- error) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case task, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
if task.txHash != nil {
|
||||
_, err = e.chain.ApplicationLog(*task.txHash)
|
||||
_, err = e.chain.Calls(*task.txHash)
|
||||
} else {
|
||||
_, err = e.chain.Block(task.index)
|
||||
}
|
||||
if err != nil {
|
||||
out <- err
|
||||
return
|
||||
}
|
||||
e.wg.Done()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < amount; i++ {
|
||||
go worker(e.ctx, e.jobCh, e.errCh)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Explorer) cacheBlocks(from, to uint32) {
|
||||
for i := from; i <= to; i++ {
|
||||
e.wg.Add(1)
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
return
|
||||
case err := <-e.errCh:
|
||||
panic(err)
|
||||
case e.jobCh <- fetchTask{index: i}:
|
||||
}
|
||||
}
|
||||
|
||||
wgCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
e.wg.Wait()
|
||||
close(wgCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
return
|
||||
case err := <-e.errCh:
|
||||
panic(err)
|
||||
case <-wgCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Explorer) cacheNotifications(block *block.Block) {
|
||||
for _, tx := range block.Transactions {
|
||||
h := tx.Hash()
|
||||
e.wg.Add(1)
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
return
|
||||
case err := <-e.errCh:
|
||||
panic(err)
|
||||
case e.jobCh <- fetchTask{txHash: &h}:
|
||||
}
|
||||
}
|
||||
|
||||
wgCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
e.wg.Wait()
|
||||
close(wgCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
return
|
||||
case err := <-e.errCh:
|
||||
panic(err)
|
||||
case <-wgCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Explorer) defaultStatusBar(input *tview.InputField, blocks int) {
|
||||
message := fmt.Sprintf("Endpoint: %s Blocks: %d | Press q to back, / to search, r to resync.",
|
||||
e.endpoint,
|
||||
blocks)
|
||||
input.SetText("").
|
||||
SetLabelColor(tcell.ColorWhite).
|
||||
SetLabel(message)
|
||||
}
|
||||
|
||||
func (e *Explorer) searchStatusBar(input *tview.InputField) {
|
||||
input.SetText("").
|
||||
SetLabelColor(tcell.ColorGreen).
|
||||
SetLabel("Search block or transaction: ")
|
||||
}
|
||||
|
||||
func (e *Explorer) errorStatusBar(input *tview.InputField, message string) {
|
||||
e.searchErrFlag = true
|
||||
input.SetText("").
|
||||
SetLabelColor(tcell.ColorRed).
|
||||
SetLabel(message)
|
||||
}
|
||||
|
||||
func (e *Explorer) displayBlockFlex(f *tview.Flex, info, counter, notif *tview.TextView, list *tview.List, b *block.Block) {
|
||||
ln := min(len(b.Transactions), 7)
|
||||
for _, tx := range b.Transactions {
|
||||
list.AddItem(tx.Hash().StringLE(), "", 0, nil)
|
||||
}
|
||||
info.SetText(fmt.Sprintf("Exploring block #%d", b.Index))
|
||||
counter.SetText(fmt.Sprintf("Transaction %d of %d\n---", min(1, ln), list.GetItemCount()))
|
||||
notif.SetBorder(true).SetTitle("Notifications")
|
||||
f.Clear()
|
||||
f.SetBorder(true)
|
||||
f.AddItem(info, 0, 1, false)
|
||||
f.AddItem(counter, 2, 2, false)
|
||||
f.AddItem(list, ln, 3, false)
|
||||
}
|
||||
|
||||
func (e *Explorer) hideBlockFlex(flex *tview.Flex, info, counter, notif *tview.TextView, list *tview.List) {
|
||||
info.SetText("")
|
||||
counter.SetText("")
|
||||
list.Clear()
|
||||
flex.SetBorder(false)
|
||||
notif.SetText("").SetBorder(false).SetTitle("")
|
||||
}
|
||||
|
||||
func toBlockIndex(index, length int) uint32 {
|
||||
return uint32(length - index - 1)
|
||||
}
|
||||
|
||||
func blockIndexRange(index, length, delta int) (uint32, uint32) {
|
||||
from := index - delta
|
||||
to := index + delta
|
||||
if from < 0 {
|
||||
from = 0
|
||||
}
|
||||
if to >= length {
|
||||
to = length - 1
|
||||
}
|
||||
return uint32(from), uint32(to)
|
||||
}
|
||||
|
||||
func formatNotification(event state.NotificationEvent) (string, error) {
|
||||
data, err := stackitem.ToJSONWithTypes(event.Item)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var formatted bytes.Buffer
|
||||
err = json.Indent(&formatted, data, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s\n---\n%s\n\n", event.Name, formatted.String()), nil
|
||||
}
|
||||
|
||||
func formatCall(syscallParams bytecode.SyscallParameters) string {
|
||||
return fmt.Sprintf("Contract: %s; Method: %s\n", syscallParams.Contract, syscallParams.Method)
|
||||
}
|
||||
|
||||
func setFocusColorStyle(target, focus *tview.Box) {
|
||||
focus.SetFocusFunc(func() {
|
||||
target.SetBorderColor(tcell.ColorGreen)
|
||||
target.SetBorderAttributes(tcell.AttrBold)
|
||||
})
|
||||
focus.SetBlurFunc(func() {
|
||||
target.SetBorderColor(tcell.ColorDefault)
|
||||
target.SetBorderAttributes(tcell.AttrNone)
|
||||
})
|
||||
}
|
183
flags.go
183
flags.go
|
@ -1,183 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
endpointFlagKey = "rpc-endpoint"
|
||||
fromFlagKey = "from"
|
||||
toFlagKey = "to"
|
||||
notificationFlagKey = "notification"
|
||||
cacheFlagKey = "cache"
|
||||
workersFlagKey = "workers"
|
||||
disableProgressBarFlagKey = "disable-progress-bar"
|
||||
stutterThresholdFlagKey = "threshold"
|
||||
forceCacheRewriteKey = "force"
|
||||
)
|
||||
|
||||
var (
|
||||
endpointFlag = &cli.StringFlag{
|
||||
Name: endpointFlagKey,
|
||||
Aliases: []string{"r"},
|
||||
Usage: "N3 RPC endpoint",
|
||||
Required: true,
|
||||
}
|
||||
|
||||
fromFlag = &cli.StringFlag{
|
||||
Name: fromFlagKey,
|
||||
Usage: "starting block (can be relative value with minus prefix, e.g. 'm100')",
|
||||
Required: true,
|
||||
Value: "",
|
||||
}
|
||||
|
||||
toFlag = &cli.StringFlag{
|
||||
Name: toFlagKey,
|
||||
Usage: "ending block (can be relative value with plus prefix, e.g. 'p100' or omitted for latest block in chain)",
|
||||
Required: false,
|
||||
Value: "",
|
||||
}
|
||||
|
||||
notificationFlag = &cli.StringSliceFlag{
|
||||
Name: notificationFlagKey,
|
||||
Aliases: []string{"n"},
|
||||
Usage: "'notification:contract' pair (specify LE script hash, '*' for any contract or 'gas' and 'neo' strings)",
|
||||
Required: true,
|
||||
Value: nil,
|
||||
}
|
||||
|
||||
cacheFlag = &cli.StringFlag{
|
||||
Name: cacheFlagKey,
|
||||
Aliases: []string{"c"},
|
||||
Usage: "path to the blockchain cache (default: $HOME/.config/monza)",
|
||||
Value: "",
|
||||
}
|
||||
|
||||
workersFlag = &cli.Uint64Flag{
|
||||
Name: workersFlagKey,
|
||||
Aliases: []string{"w"},
|
||||
Usage: "amount of workers for parallel block fetch",
|
||||
Value: 3,
|
||||
}
|
||||
|
||||
disableProgressBarFlag = &cli.BoolFlag{
|
||||
Name: disableProgressBarFlagKey,
|
||||
Usage: "disable progress bar output",
|
||||
}
|
||||
|
||||
stutterThresholdFlag = &cli.DurationFlag{
|
||||
Name: stutterThresholdFlagKey,
|
||||
Aliases: []string{"t"},
|
||||
Usage: "duration limit between block timestamps",
|
||||
Value: 20 * time.Second,
|
||||
}
|
||||
|
||||
forceCacheRewriteFlag = &cli.BoolFlag{
|
||||
Name: forceCacheRewriteKey,
|
||||
Aliases: []string{"f"},
|
||||
Usage: "force blockchain cache rewrite",
|
||||
}
|
||||
)
|
||||
|
||||
func parseNotifications(notifications []string, cli *rpcclient.Client) (map[string]*util.Uint160, error) {
|
||||
res := make(map[string]*util.Uint160, len(notifications))
|
||||
|
||||
for _, n := range notifications {
|
||||
pair := strings.Split(n, ":")
|
||||
if len(pair) != 2 {
|
||||
return nil, fmt.Errorf("invalid notification %s", n)
|
||||
}
|
||||
|
||||
name := pair[0]
|
||||
|
||||
switch contractName := strings.ToLower(pair[1]); contractName {
|
||||
case "*":
|
||||
res[name] = nil
|
||||
case "gas":
|
||||
state, err := cli.GetContractStateByAddressOrName(nativenames.Gas)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid contract name %s", contractName)
|
||||
}
|
||||
res[name] = &state.Hash
|
||||
case "neo":
|
||||
state, err := cli.GetContractStateByAddressOrName(nativenames.Neo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid contract name %s", contractName)
|
||||
}
|
||||
res[name] = &state.Hash
|
||||
default:
|
||||
u160, err := util.Uint160DecodeStringLE(contractName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid contract name %s", contractName)
|
||||
}
|
||||
res[name] = &u160
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func parseInterval(fromStr, toStr string, cli *rpcclient.Client) (from, to uint32, err error) {
|
||||
switch { // parse from value and return result if it is relative
|
||||
case len(fromStr) == 0:
|
||||
return 0, 0, ErrInvalidInterval(fromStr, toStr)
|
||||
case fromStr[0] == 'm':
|
||||
v, err := strconv.Atoi(fromStr[1:])
|
||||
if err != nil || v <= 0 {
|
||||
return 0, 0, ErrInvalidInterval(fromStr, toStr)
|
||||
}
|
||||
h, err := cli.GetBlockCount()
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("latest block index unavailable: %w", err)
|
||||
}
|
||||
if uint32(v) >= h {
|
||||
return 0, 0, fmt.Errorf("latest block is less than from value, from:%s, to:%d", fromStr, h)
|
||||
}
|
||||
return h - uint32(v), h, nil
|
||||
default:
|
||||
v, err := strconv.Atoi(fromStr)
|
||||
if err != nil || v <= 0 {
|
||||
return 0, 0, ErrInvalidInterval(fromStr, toStr)
|
||||
}
|
||||
from = uint32(v)
|
||||
}
|
||||
|
||||
switch { // parse to value
|
||||
case len(toStr) == 0:
|
||||
h, err := cli.GetBlockCount()
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("latest block index unavailable: %w", err)
|
||||
}
|
||||
if h <= from {
|
||||
return 0, 0, fmt.Errorf("latest block is less than from value, from:%d, to:%d", from, h)
|
||||
}
|
||||
return from, h, nil
|
||||
case toStr[0] == 'p':
|
||||
v, err := strconv.Atoi(toStr[1:])
|
||||
if err != nil || v <= 0 {
|
||||
return 0, 0, ErrInvalidInterval(fromStr, toStr)
|
||||
}
|
||||
return from, from + uint32(v), nil
|
||||
default:
|
||||
v, err := strconv.Atoi(toStr)
|
||||
if err != nil || v <= 0 {
|
||||
return 0, 0, ErrInvalidInterval(fromStr, toStr)
|
||||
}
|
||||
if uint32(v) <= from {
|
||||
return 0, 0, ErrInvalidInterval(fromStr, toStr)
|
||||
}
|
||||
return from, uint32(v), nil
|
||||
}
|
||||
}
|
||||
|
||||
func ErrInvalidInterval(from, to string) error {
|
||||
return fmt.Errorf("invalid block interval from:%s to:%s", from, to)
|
||||
}
|
42
go.mod
42
go.mod
|
@ -1,42 +0,0 @@
|
|||
module git.frostfs.info/TrueCloudLab/monza
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell/v2 v2.5.1
|
||||
github.com/nspcc-dev/neo-go v0.102.0
|
||||
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8
|
||||
github.com/schollz/progressbar/v3 v3.8.3
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.6.0 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 // indirect
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/term v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
108
go.sum
108
go.sum
|
@ -1,108 +0,0 @@
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04=
|
||||
github.com/gdamore/tcell/v2 v2.5.1 h1:zc3LPdpK184lBW7syF2a5C6MV827KmErk9jGVnmsl/I=
|
||||
github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
|
||||
github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 h1:n4ZaFCKt1pQJd7PXoMJabZWK9ejjbLOVrkl/lOUmshg=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22/go.mod h1:79bEUDEviBHJMFV6Iq6in57FEOCMcRhfQnfaf0ETA5U=
|
||||
github.com/nspcc-dev/neo-go v0.102.0 h1:O2Gt4JPOWmp0c+PnPWwd2wPI74BKSwkaNCEyvyQTWJw=
|
||||
github.com/nspcc-dev/neo-go v0.102.0/go.mod h1:QXxpZxJT2KedwM0Nlj8UO0/fZN2WIe4h/i03uBHKbnc=
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE=
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 h1:xe+mmCnDN82KhC010l3NfYlA8ZbOuzbXAzSYBa6wbMc=
|
||||
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/schollz/progressbar/v3 v3.8.3 h1:FnLGl3ewlDUP+YdSwveXBaXs053Mem/du+wr7XSYKl8=
|
||||
github.com/schollz/progressbar/v3 v3.8.3/go.mod h1:pWnVCjSBZsT2X3nx9HfRdnCDrpbevliMeoEVhStwHko=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
|
||||
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,146 +0,0 @@
|
|||
package bytecode
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
)
|
||||
|
||||
type SyscallParameters struct {
|
||||
Contract string `json:"contract"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
// ExtractCalls Extract parameters of SYSCALL opcode from bytecode of transaction
|
||||
func ExtractCalls(script []byte) ([]SyscallParameters, error) {
|
||||
syscallParams := make([]SyscallParameters, 0)
|
||||
|
||||
offsets := make([]int, 0)
|
||||
for i := 0; i < len(script); {
|
||||
offsets = append(offsets, i)
|
||||
|
||||
if opcode.Opcode(script[i]) == opcode.SYSCALL {
|
||||
// Current opcode is SYSCALL
|
||||
// here we parse two previous instruction,
|
||||
// slice offsets contains positions of these two instructions
|
||||
//
|
||||
// PUSHDATA1/2/4 <Len in bytes of name> <Name of method>
|
||||
// PUSHDATA1 <Len in bytes of address> <Address of contract>
|
||||
// SYSCALL System.Contract.Call
|
||||
|
||||
offset1 := offsets[len(offsets)-3]
|
||||
offset2 := offsets[len(offsets)-2]
|
||||
|
||||
offset1++
|
||||
var n int
|
||||
switch opcode.Opcode(script[offset1-1]) {
|
||||
case opcode.PUSHDATA1:
|
||||
n = parseInt(offset1, 1, script)
|
||||
offset1 += 1
|
||||
case opcode.PUSHDATA2:
|
||||
n = parseInt(offset1, 2, script)
|
||||
offset1 += 2
|
||||
case opcode.PUSHDATA4:
|
||||
n = parseInt(offset1, 4, script)
|
||||
offset1 += 4
|
||||
}
|
||||
src := script[offset1 : offset1+n]
|
||||
|
||||
nameOfMethod, err := hex.DecodeString(hex.EncodeToString(src))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert bytes to UTF-8 string: %w", err)
|
||||
}
|
||||
|
||||
offset2++
|
||||
var contractAddress string
|
||||
switch opcode.Opcode(script[offset2-1]) {
|
||||
case opcode.PUSHDATA1:
|
||||
n = parseInt(offset2, 1, script)
|
||||
if n != 20 {
|
||||
return nil, errors.New("invalid bytecode: address should be 20-byte value")
|
||||
}
|
||||
offset2++
|
||||
u, err := util.Uint160DecodeBytesBE(script[offset2 : offset2+20])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode bytes BE to Uint160: %w", err)
|
||||
}
|
||||
contractAddress = address.Uint160ToString(u)
|
||||
default:
|
||||
return nil, errors.New("invalid bytecode: address should be 20-byte value")
|
||||
}
|
||||
|
||||
syscallParams = append(syscallParams, SyscallParameters{
|
||||
Contract: contractAddress,
|
||||
Method: string(nameOfMethod),
|
||||
})
|
||||
}
|
||||
|
||||
// Skip n bytes (params of current opcode) to next opcode
|
||||
err := skipBytes(&i, script)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s%w", "error while parsing bytecode:", err)
|
||||
}
|
||||
}
|
||||
|
||||
return syscallParams, nil
|
||||
}
|
||||
|
||||
func skipBytes(i *int, script []byte) error {
|
||||
*i++
|
||||
switch opcode.Opcode(script[*i-1]) {
|
||||
case opcode.PUSHINT8:
|
||||
*i += 1
|
||||
case opcode.PUSHINT16:
|
||||
*i += 2
|
||||
case opcode.PUSHINT32:
|
||||
*i += 4
|
||||
case opcode.PUSHINT64:
|
||||
*i += 8
|
||||
case opcode.PUSHINT128:
|
||||
*i += 16
|
||||
case opcode.PUSHINT256:
|
||||
*i += 32
|
||||
case opcode.PUSHDATA1:
|
||||
n := parseInt(*i, 1, script)
|
||||
*i += n + 1
|
||||
case opcode.PUSHDATA2:
|
||||
n := parseInt(*i, 2, script)
|
||||
*i += n + 2
|
||||
case opcode.PUSHDATA4:
|
||||
n := parseInt(*i, 4, script)
|
||||
*i += n + 4
|
||||
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
|
||||
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
|
||||
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
|
||||
opcode.ENDTRY:
|
||||
*i += 1
|
||||
case opcode.INITSLOT, opcode.TRY, opcode.CALLT:
|
||||
*i += 2
|
||||
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
||||
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
||||
opcode.ENDTRYL,
|
||||
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
|
||||
*i += 4
|
||||
case opcode.TRYL:
|
||||
*i += 8
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseInt(i int, len int, b []byte) int {
|
||||
switch len {
|
||||
case 1:
|
||||
return int(b[i])
|
||||
case 2:
|
||||
return int(binary.LittleEndian.Uint16(b[i : i+len]))
|
||||
case 4:
|
||||
return int(binary.LittleEndian.Uint32(b[i : i+len]))
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
package bytecode
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func fromHexadecimalStringToBytes(str string) []byte {
|
||||
data, _ := hex.DecodeString(str)
|
||||
return data
|
||||
}
|
||||
|
||||
type ExtractCallTest struct {
|
||||
name string
|
||||
script string
|
||||
bytecodeStr string
|
||||
expected []SyscallParameters
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
var tests = []ExtractCallTest{
|
||||
{
|
||||
name: "valid bytecode 1",
|
||||
script: "PUSHINT16 3600\n" +
|
||||
"PUSHINT32 315360000\n" +
|
||||
"PUSHINT16 600\n" +
|
||||
"PUSHINT16 3600\n" +
|
||||
"PUSHDATA1 6f70734066726f737466732e696e666f\n" +
|
||||
"PUSHDATA1 0x56c989e76f9a2ca05bb5caa6c96f524d905accd8\n" +
|
||||
"PUSHDATA1 frostfs\n" +
|
||||
"PUSH7\n" +
|
||||
"PACK\n" +
|
||||
"PUSH15\n" +
|
||||
"PUSHDATA1 register\n" +
|
||||
"PUSHDATA1 0x76c10d0295b7e76a5674c55d702f04ce280e745e\n" +
|
||||
"SYSCALL System.Contract.Call\n" +
|
||||
"ASSERT",
|
||||
bytecodeStr: "0c106f70734066726f737466" +
|
||||
"732e696e666f0c14d8cc5a90" +
|
||||
"4d526fc9a6cab55ba02c9a6f" +
|
||||
"e789c9560c0766726f737466" +
|
||||
"7317c01f0c08726567697374" +
|
||||
"65720c145e740e28ce042f70" +
|
||||
"5dc574566ae7b795020dc176" +
|
||||
"41627d5b5239",
|
||||
expected: []SyscallParameters{
|
||||
{
|
||||
Contract: "NUXPjTBsLY6fgVPfmBf8uLk9QBVqgKKkdB",
|
||||
Method: "register",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid bytecode 2",
|
||||
script: "PUSHNULL\n" +
|
||||
"PUSHINT32 100000000\n" +
|
||||
"PUSHDATA1 0x0945e5d5d2dae45c095a4f66f4d48ccba1e512db\n" +
|
||||
"PUSHDATA1 0x56c989e76f9a2ca05bb5caa6c96f524d905accd8\n" +
|
||||
"PUSH4\n" +
|
||||
"PACK\n" +
|
||||
"PUSH15\n" +
|
||||
"PUSHDATA1 transfer\n" +
|
||||
"PUSHDATA1 0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5\n" +
|
||||
"SYSCALL System.Contract.Call\n" +
|
||||
"ASSERT",
|
||||
bytecodeStr: "0b0200e1f5050c14db12e5a1" +
|
||||
"cb8cd4f4664f5a095ce4dad2" +
|
||||
"d5e545090c14d8cc5a904d52" +
|
||||
"6fc9a6cab55ba02c9a6fe789" +
|
||||
"c95614c01f0c087472616e73" +
|
||||
"6665720c14f563ea40bc283d" +
|
||||
"4d0e05c48ea305b3f2a07340" +
|
||||
"ef41627d5b5239",
|
||||
expected: []SyscallParameters{
|
||||
{
|
||||
Contract: "NiHURyS83nX2mpxtA7xq84cGxVbHojj5Wc",
|
||||
Method: "transfer",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple calls",
|
||||
script: "PUSH1\n" +
|
||||
"PUSH1\n" +
|
||||
"PACK\n" +
|
||||
"PUSH3\n" +
|
||||
"PUSHDATA1 setRegisterPrice\n" +
|
||||
"PUSHDATA1 0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5\n" +
|
||||
"SYSCALL System.Contract.Call\n" +
|
||||
"PUSHDATA1 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2\n" +
|
||||
"PUSH1\n" +
|
||||
"PACK\n" +
|
||||
"PUSH3\n" +
|
||||
"PUSHDATA1 registerCandidate\n" +
|
||||
"PUSHDATA1 0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5\n" +
|
||||
"SYSCALL System.Contract.Call\n" +
|
||||
"ASSERT\n" +
|
||||
"PUSHINT64 100000000000\n" +
|
||||
"PUSH1\n" +
|
||||
"PACK\n" +
|
||||
"PUSH3\n" +
|
||||
"PUSHDATA1 setRegisterPrice\n" +
|
||||
"PUSHDATA1 0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5\n" +
|
||||
"SYSCALL System.Contract.Call",
|
||||
bytecodeStr: "1111c0130c10736574526567" +
|
||||
"697374657250726963650c14" +
|
||||
"f563ea40bc283d4d0e05c48e" +
|
||||
"a305b3f2a07340ef41627d5b" +
|
||||
"520c2102b3622bf4017bdfe3" +
|
||||
"17c58aed5f4c753f206b7db8" +
|
||||
"96046fa7d774bbc4bf7f8dc2" +
|
||||
"11c0130c1172656769737465" +
|
||||
"7243616e6469646174650c14" +
|
||||
"f563ea40bc283d4d0e05c48e" +
|
||||
"a305b3f2a07340ef41627d5b" +
|
||||
"52390300e876481700000011" +
|
||||
"c0130c107365745265676973" +
|
||||
"74657250726963650c14f563" +
|
||||
"ea40bc283d4d0e05c48ea305" +
|
||||
"b3f2a07340ef41627d5b52",
|
||||
expected: []SyscallParameters{
|
||||
{
|
||||
Contract: "NiHURyS83nX2mpxtA7xq84cGxVbHojj5Wc",
|
||||
Method: "setRegisterPrice",
|
||||
},
|
||||
{
|
||||
Contract: "NiHURyS83nX2mpxtA7xq84cGxVbHojj5Wc",
|
||||
Method: "registerCandidate",
|
||||
},
|
||||
{
|
||||
Contract: "NiHURyS83nX2mpxtA7xq84cGxVbHojj5Wc",
|
||||
Method: "setRegisterPrice",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid contract address len",
|
||||
script: "PUSH1\n" +
|
||||
"PUSH1\n" +
|
||||
"PACK\n" +
|
||||
"PUSH3\n" +
|
||||
"PUSHDATA1 setRegisterPrice\n" +
|
||||
"PUSHDATA1 a1ef4073a0f2b305a38ec4050e4d3d28bc40ea63f5\n" +
|
||||
"SYSCALL System.Contract.Call",
|
||||
bytecodeStr: "1111c0130c1073657452656" +
|
||||
"7697374657250726963650c" +
|
||||
"14f563ea40bc283d4d0e05c" +
|
||||
"48ea305b3f2a07340efa141" +
|
||||
"627d5b52",
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
func TestExtractCalls(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
bytecode := fromHexadecimalStringToBytes(tt.bytecodeStr)
|
||||
syscallParams, err := ExtractCalls(bytecode)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
}
|
||||
require.Equal(t, tt.expected, syscallParams)
|
||||
}
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/monza/internal/bytecode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
type Chain struct {
|
||||
db *bbolt.DB
|
||||
stateRoot bool
|
||||
Client *rpcclient.Client
|
||||
}
|
||||
|
||||
var (
|
||||
blocksBucket = []byte("blocks")
|
||||
logsBucket = []byte("logs")
|
||||
callsBucket = []byte("calls")
|
||||
)
|
||||
|
||||
func Open(ctx context.Context, dir, endpoint string, rewrite bool) (*Chain, error) {
|
||||
cli, err := rpcclient.New(ctx, endpoint, rpcclient.Options{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rpc connection: %w", err)
|
||||
}
|
||||
|
||||
err = cli.Init()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rpc client initialization: %w", err)
|
||||
}
|
||||
|
||||
v, err := cli.GetVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rpc get version: %w", err)
|
||||
}
|
||||
|
||||
validationBlock, err := getValidationBlock(cli)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error get validation block: %w", err)
|
||||
}
|
||||
|
||||
dbPath := path.Join(dir, validationBlock.Hash().StringLE()+".db")
|
||||
if rewrite {
|
||||
_ = os.Remove(dbPath) // ignore error if file does not exist, etc.
|
||||
}
|
||||
|
||||
db, err := bbolt.Open(dbPath, 0600, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("database [%s] init: %w", dbPath, err)
|
||||
}
|
||||
|
||||
return &Chain{db, v.Protocol.StateRootInHeader, cli}, nil
|
||||
}
|
||||
|
||||
func getValidationBlock(cli *rpcclient.Client) (*block.Block, error) {
|
||||
// not 0, because it always has the same hash
|
||||
const validationBlockIndex = 1
|
||||
var err error
|
||||
validBlock, err := cli.GetBlockByIndex(validationBlockIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error sending request for block: %w", err)
|
||||
}
|
||||
|
||||
return validBlock, nil
|
||||
}
|
||||
|
||||
func (d *Chain) Block(i uint32) (res *block.Block, err error) {
|
||||
cached, err := d.block(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cached != nil {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
metaBlock, err := d.Client.GetBlockByIndexVerbose(i)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("block %d fetch: %w", i, err)
|
||||
}
|
||||
|
||||
return &metaBlock.Block, d.addBlock(&metaBlock.Block)
|
||||
}
|
||||
|
||||
func (d *Chain) BlockByHash(h util.Uint256) (res *block.Block, err error) {
|
||||
rev := h.Reverse()
|
||||
metaBlock, err := d.Client.GetBlockByHashVerbose(rev)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("block %s fetch: %w", h.StringLE(), err)
|
||||
}
|
||||
|
||||
return &metaBlock.Block, d.addBlock(&metaBlock.Block)
|
||||
}
|
||||
|
||||
func (d *Chain) block(i uint32) (res *block.Block, err error) {
|
||||
err = d.db.View(func(tx *bbolt.Tx) error {
|
||||
key := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(key, i)
|
||||
|
||||
bkt := tx.Bucket(blocksBucket)
|
||||
if bkt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := bkt.Get(key)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
res = block.New(d.stateRoot)
|
||||
r := io.NewBinReaderFromBuf(data)
|
||||
res.DecodeBinary(r)
|
||||
|
||||
return r.Err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read block %d from cache: %w", i, err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Chain) addBlock(block *block.Block) error {
|
||||
err := d.db.Batch(func(tx *bbolt.Tx) error {
|
||||
key := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(key, block.Index)
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
block.EncodeBinary(w.BinWriter)
|
||||
if w.Err != nil {
|
||||
return w.Err
|
||||
}
|
||||
|
||||
bkt, err := tx.CreateBucketIfNotExists(blocksBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bkt.Put(key, w.Bytes())
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot add block %d to cache: %w", block.Index, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Chain) ApplicationLog(txHash util.Uint256) (*result.ApplicationLog, error) {
|
||||
cached, err := d.applicationLog(txHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cached != nil {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
appLog, err := d.Client.GetApplicationLog(txHash, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("app log of tx %s fetch: %w", txHash.StringLE(), err)
|
||||
}
|
||||
|
||||
return appLog, d.addApplicationLog(txHash, appLog)
|
||||
}
|
||||
|
||||
func (d *Chain) Calls(txHash util.Uint256) ([]bytecode.SyscallParameters, error) {
|
||||
cached, err := d.calls(txHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cached != nil {
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
rawTransaction, err := d.Client.GetRawTransaction(txHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
script := rawTransaction.Script
|
||||
extractedCalls, err := bytecode.ExtractCalls(script)
|
||||
|
||||
return extractedCalls, d.addCalls(txHash, extractedCalls)
|
||||
}
|
||||
|
||||
func (d *Chain) addCalls(txHash util.Uint256, syscallParams []bytecode.SyscallParameters) error {
|
||||
err := d.db.Batch(func(tx *bbolt.Tx) error {
|
||||
val, err := json.Marshal(syscallParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bkt, err := tx.CreateBucketIfNotExists(callsBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bkt.Put(txHash.BytesLE(), val)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot add tx %s to cache: %w", txHash.StringLE(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Chain) calls(txHash util.Uint256) (res []bytecode.SyscallParameters, err error) {
|
||||
err = d.db.View(func(tx *bbolt.Tx) error {
|
||||
bkt := tx.Bucket(callsBucket)
|
||||
if bkt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := bkt.Get(txHash.BytesLE())
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(bkt.Get(txHash.BytesLE()), &res)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read tx %s from cache: %w", txHash.StringLE(), err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Chain) applicationLog(txHash util.Uint256) (res *result.ApplicationLog, err error) {
|
||||
err = d.db.View(func(tx *bbolt.Tx) error {
|
||||
bkt := tx.Bucket(logsBucket)
|
||||
if bkt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := bkt.Get(txHash.BytesLE())
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
res = new(result.ApplicationLog)
|
||||
return res.UnmarshalJSON(bkt.Get(txHash.BytesLE()))
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read tx %s from cache: %w", txHash.StringLE(), err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Chain) addApplicationLog(txHash util.Uint256, appLog *result.ApplicationLog) error {
|
||||
err := d.db.Batch(func(tx *bbolt.Tx) error {
|
||||
val, err := appLog.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bkt, err := tx.CreateBucketIfNotExists(logsBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bkt.Put(txHash.BytesLE(), val)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot add tx %s to cache: %w", txHash.StringLE(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Chain) Notifications(txHash util.Uint256) ([]state.NotificationEvent, error) {
|
||||
appLog, err := d.ApplicationLog(txHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]state.NotificationEvent, 0)
|
||||
for _, execution := range appLog.Executions {
|
||||
res = append(res, execution.Events...)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Chain) AllNotifications(b *block.Block) ([]state.NotificationEvent, error) {
|
||||
res := make([]state.NotificationEvent, 0)
|
||||
|
||||
appLog, err := d.ApplicationLog(b.Hash())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, execution := range appLog.Executions {
|
||||
res = append(res, execution.Events...)
|
||||
}
|
||||
|
||||
for _, tx := range b.Transactions {
|
||||
appLog, err = d.ApplicationLog(tx.Hash())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, execution := range appLog.Executions {
|
||||
res = append(res, execution.Events...)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Chain) Close() {
|
||||
_ = d.db.Close()
|
||||
}
|
274
main.go
274
main.go
|
@ -1,274 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/monza/internal/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Name: "monza",
|
||||
Usage: "monitor notification events in N3 compatible chains",
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "run",
|
||||
Usage: "look up over subset of blocks to find notifications",
|
||||
UsageText: "monza run -r [endpoint] --from 101000 --to p1000 -n \"Transfer:gas\" -n \"newEpoch:*\"",
|
||||
Action: monza,
|
||||
Flags: []cli.Flag{
|
||||
endpointFlag,
|
||||
fromFlag,
|
||||
toFlag,
|
||||
notificationFlag,
|
||||
cacheFlag,
|
||||
workersFlag,
|
||||
disableProgressBarFlag,
|
||||
forceCacheRewriteFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "stutter",
|
||||
Usage: "find stuttered blocks in subset",
|
||||
UsageText: "monza stutter -r [endpoint] --from 101000 --to p1000 --threshold 20s",
|
||||
Action: stutter,
|
||||
Flags: []cli.Flag{
|
||||
endpointFlag,
|
||||
fromFlag,
|
||||
toFlag,
|
||||
stutterThresholdFlag,
|
||||
cacheFlag,
|
||||
workersFlag,
|
||||
disableProgressBarFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "explore",
|
||||
Usage: "explore stuttered blocks in subset",
|
||||
UsageText: "monza explore -r [endpoint]",
|
||||
Action: explorer,
|
||||
Flags: []cli.Flag{
|
||||
endpointFlag,
|
||||
cacheFlag,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func monza(c *cli.Context) (err error) {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
|
||||
// parse blockchain info
|
||||
cacheDir := c.String(cacheFlagKey)
|
||||
if len(cacheDir) == 0 {
|
||||
cacheDir, err = defaultConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
blockchain, err := chain.Open(ctx, cacheDir, c.String(endpointFlagKey), c.Bool(forceCacheRewriteKey))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot initialize remote blockchain client: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
blockchain.Close()
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// parse block indices
|
||||
from, to, err := parseInterval(c.String(fromFlagKey), c.String(toFlagKey), blockchain.Client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse notifications
|
||||
notifications, err := parseNotifications(c.StringSlice(notificationFlagKey), blockchain.Client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// start monza
|
||||
return run(ctx, ¶ms{
|
||||
from: from,
|
||||
to: to,
|
||||
blockchain: blockchain,
|
||||
notifications: notifications,
|
||||
workers: int(c.Uint64(workersFlagKey)),
|
||||
disableBar: c.Bool(disableProgressBarFlagKey),
|
||||
})
|
||||
}
|
||||
|
||||
type params struct {
|
||||
from, to uint32
|
||||
blockchain *chain.Chain
|
||||
notifications map[string]*util.Uint160
|
||||
workers int
|
||||
disableBar bool
|
||||
}
|
||||
|
||||
func run(ctx context.Context, p *params) error {
|
||||
err := cacheBlocks(ctx, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := p.from; i < p.to; i++ {
|
||||
b, err := p.blockchain.Block(i)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot fetch block %d: %w", i, err)
|
||||
}
|
||||
|
||||
notifications, err := p.blockchain.AllNotifications(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot fetch notifications from block %d: %w", i, err)
|
||||
}
|
||||
|
||||
for _, ev := range notifications {
|
||||
contract, ok := p.notifications[ev.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if contract != nil && !contract.Equals(ev.ScriptHash) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch ev.Name {
|
||||
case "Transfer":
|
||||
PrintTransfer(b, ev)
|
||||
case "NewEpoch":
|
||||
PrintNewEpoch(b, ev)
|
||||
case "AddPeerSuccess":
|
||||
PrintAddPeerSuccess(b, ev)
|
||||
case "UpdateState":
|
||||
PrintUpdateState(b, ev)
|
||||
default:
|
||||
PrintEvent(b, ev, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cacheBlocks(ctx context.Context, p *params) error {
|
||||
if p.workers <= 0 {
|
||||
return fmt.Errorf("invalid amount of workers %d", p.workers)
|
||||
}
|
||||
|
||||
var bar *progressbar.ProgressBar
|
||||
if !p.disableBar {
|
||||
bar = progressbar.NewOptions(int(p.to-p.from),
|
||||
progressbar.OptionSetDescription("syncing"),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionSetWidth(10),
|
||||
progressbar.OptionThrottle(65*time.Millisecond),
|
||||
progressbar.OptionShowCount(),
|
||||
progressbar.OptionShowIts(),
|
||||
progressbar.OptionSetItsString("blocks"),
|
||||
progressbar.OptionOnCompletion(func() {
|
||||
_, _ = fmt.Fprint(os.Stderr, "\n")
|
||||
}),
|
||||
progressbar.OptionSpinnerType(14),
|
||||
progressbar.OptionSetWidth(50),
|
||||
progressbar.OptionSetTheme(progressbar.Theme{
|
||||
Saucer: "#",
|
||||
SaucerHead: "#",
|
||||
SaucerPadding: " ",
|
||||
BarStart: "[",
|
||||
BarEnd: "]",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
jobCh := make(chan uint32)
|
||||
errCh := make(chan error)
|
||||
wgCh := make(chan struct{})
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
|
||||
for i := 0; i < p.workers; i++ {
|
||||
go func(ctx context.Context, ch <-chan uint32, out chan<- error) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case block, ok := <-ch:
|
||||
wg.Add(1)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
b, err := p.blockchain.Block(block)
|
||||
if err != nil {
|
||||
out <- err
|
||||
return
|
||||
}
|
||||
_, err = p.blockchain.AllNotifications(b)
|
||||
if err != nil {
|
||||
out <- err
|
||||
return
|
||||
}
|
||||
if bar != nil {
|
||||
_ = bar.Add(1)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
}
|
||||
}(ctx, jobCh, errCh)
|
||||
}
|
||||
|
||||
for i := p.from; i < p.to; i++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("interrupted")
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case jobCh <- i:
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(wgCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("interrupted")
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-wgCh:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func defaultConfigDir() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Fatalf("cannot determine home dir for default config path: %s", err)
|
||||
}
|
||||
|
||||
p := path.Join(home, ".config")
|
||||
p = path.Join(p, "monza")
|
||||
|
||||
return p, os.MkdirAll(p, os.ModePerm)
|
||||
}
|
98
stutter.go
98
stutter.go
|
@ -1,98 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/monza/internal/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func stutter(c *cli.Context) (err error) {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
|
||||
// parse blockchain info
|
||||
cacheDir := c.String(cacheFlagKey)
|
||||
if len(cacheDir) == 0 {
|
||||
cacheDir, err = defaultConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
blockchain, err := chain.Open(ctx, cacheDir, c.String(endpointFlagKey), false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot initialize remote blockchain client: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
blockchain.Close()
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// parse block indices
|
||||
from, to, err := parseInterval(c.String(fromFlagKey), c.String(toFlagKey), blockchain.Client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
threshold := c.Duration(stutterThresholdFlagKey)
|
||||
|
||||
// need at least two blocks
|
||||
if to-from < 2 {
|
||||
return errors.New("range must contain at least two blocks")
|
||||
}
|
||||
|
||||
// fetch blocks
|
||||
err = cacheBlocks(ctx, ¶ms{
|
||||
from: from,
|
||||
to: to,
|
||||
blockchain: blockchain,
|
||||
workers: int(c.Uint64(workersFlagKey)),
|
||||
disableBar: c.Bool(disableProgressBarFlagKey),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// process blocks one by one
|
||||
var (
|
||||
prev, curr *block.Block
|
||||
prevTS, currTS time.Time
|
||||
lastStutterBlock uint32
|
||||
)
|
||||
|
||||
for i := from; i < to; i++ {
|
||||
b, err := blockchain.Block(i)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot fetch block %d: %w", i, err)
|
||||
}
|
||||
|
||||
prev, prevTS = curr, currTS
|
||||
curr = b
|
||||
currTS = time.Unix(int64(b.Timestamp/1e3), 0)
|
||||
if prev == nil { // first block case
|
||||
continue
|
||||
}
|
||||
|
||||
blockDelta := currTS.Sub(prevTS)
|
||||
if blockDelta <= threshold {
|
||||
continue
|
||||
}
|
||||
|
||||
skippedBlocks := prev.Index - lastStutterBlock
|
||||
if lastStutterBlock > 0 && skippedBlocks > 1 {
|
||||
fmt.Printf("-- skipped %d blocks --\n", skippedBlocks-1)
|
||||
}
|
||||
|
||||
PrintBlock(prev, "")
|
||||
PrintBlock(curr, fmt.Sprintf("<- stutter for %s", blockDelta))
|
||||
lastStutterBlock = curr.Index
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Reference in a new issue