monza/internal/chain/chain.go
AndrewDanilin aca2229ab7 [#2] Print name of invoked method in transaction
Signed-off-by: AndrewDanilin <andnilin@gmail.com>
2024-07-03 09:36:54 +03:00

309 lines
6.6 KiB
Go

package chain
import (
"context"
"encoding/binary"
"encoding/json"
"fmt"
"os"
"path"
"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/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)
}
dbPath := path.Join(dir, strconv.Itoa(int(v.Protocol.Network))+".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 (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()
}