forked from TrueCloudLab/monza
[#2] Print name of invoked method in transaction
Signed-off-by: AndrewDanilin <andnilin@gmail.com>
This commit is contained in:
parent
4fc7478e1e
commit
aca2229ab7
6 changed files with 401 additions and 0 deletions
13
explorer.go
13
explorer.go
|
@ -12,6 +12,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/monza/internal/bytecode"
|
||||||
"git.frostfs.info/TrueCloudLab/monza/internal/chain"
|
"git.frostfs.info/TrueCloudLab/monza/internal/chain"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
|
@ -248,6 +249,13 @@ func (e *Explorer) Run() error {
|
||||||
}
|
}
|
||||||
res += v
|
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.SetText(res)
|
||||||
notifications.ScrollToBeginning()
|
notifications.ScrollToBeginning()
|
||||||
})
|
})
|
||||||
|
@ -346,6 +354,7 @@ func (e *Explorer) startWorkers(amount int) {
|
||||||
var err error
|
var err error
|
||||||
if task.txHash != nil {
|
if task.txHash != nil {
|
||||||
_, err = e.chain.ApplicationLog(*task.txHash)
|
_, err = e.chain.ApplicationLog(*task.txHash)
|
||||||
|
_, err = e.chain.Calls(*task.txHash)
|
||||||
} else {
|
} else {
|
||||||
_, err = e.chain.Block(task.index)
|
_, err = e.chain.Block(task.index)
|
||||||
}
|
}
|
||||||
|
@ -496,6 +505,10 @@ func formatNotification(event state.NotificationEvent) (string, error) {
|
||||||
return fmt.Sprintf("%s\n---\n%s\n\n", event.Name, formatted.String()), nil
|
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) {
|
func setFocusColorStyle(target, focus *tview.Box) {
|
||||||
focus.SetFocusFunc(func() {
|
focus.SetFocusFunc(func() {
|
||||||
target.SetBorderColor(tcell.ColorGreen)
|
target.SetBorderColor(tcell.ColorGreen)
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -7,12 +7,14 @@ require (
|
||||||
github.com/nspcc-dev/neo-go v0.102.0
|
github.com/nspcc-dev/neo-go v0.102.0
|
||||||
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8
|
github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8
|
||||||
github.com/schollz/progressbar/v3 v3.8.3
|
github.com/schollz/progressbar/v3 v3.8.3
|
||||||
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
go.etcd.io/bbolt v1.3.7
|
go.etcd.io/bbolt v1.3.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
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/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||||
github.com/gdamore/encoding v1.0.0 // indirect
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
@ -25,6 +27,7 @@ require (
|
||||||
github.com/mr-tron/base58 v1.2.0 // 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/go-ordered-json v0.0.0-20220111165707-25110be27d22 // indirect
|
||||||
github.com/nspcc-dev/rfc6979 v0.2.0 // 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/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -54,7 +54,11 @@ github.com/schollz/progressbar/v3 v3.8.3/go.mod h1:pWnVCjSBZsT2X3nx9HfRdnCDrpbev
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
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/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.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.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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
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 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
|
||||||
|
@ -99,5 +103,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
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/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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
146
internal/bytecode/extract.go
Normal file
146
internal/bytecode/extract.go
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
168
internal/bytecode/extract_test.go
Normal file
168
internal/bytecode/extract_test.go
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,13 @@ package chain
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/monza/internal/bytecode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"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/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
@ -26,6 +28,7 @@ type Chain struct {
|
||||||
var (
|
var (
|
||||||
blocksBucket = []byte("blocks")
|
blocksBucket = []byte("blocks")
|
||||||
logsBucket = []byte("logs")
|
logsBucket = []byte("logs")
|
||||||
|
callsBucket = []byte("calls")
|
||||||
)
|
)
|
||||||
|
|
||||||
func Open(ctx context.Context, dir, endpoint string, rewrite bool) (*Chain, error) {
|
func Open(ctx context.Context, dir, endpoint string, rewrite bool) (*Chain, error) {
|
||||||
|
@ -156,6 +159,69 @@ func (d *Chain) ApplicationLog(txHash util.Uint256) (*result.ApplicationLog, err
|
||||||
return appLog, d.addApplicationLog(txHash, appLog)
|
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) {
|
func (d *Chain) applicationLog(txHash util.Uint256) (res *result.ApplicationLog, err error) {
|
||||||
err = d.db.View(func(tx *bbolt.Tx) error {
|
err = d.db.View(func(tx *bbolt.Tx) error {
|
||||||
bkt := tx.Bucket(logsBucket)
|
bkt := tx.Bucket(logsBucket)
|
||||||
|
|
Loading…
Reference in a new issue