Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
f6d52cd8e3 |
11 changed files with 1775 additions and 0 deletions
112
app/README.md
Normal file
112
app/README.md
Normal file
|
@ -0,0 +1,112 @@
|
|||
# Nyan cat example
|
||||
|
||||
## Prerequisite
|
||||
|
||||
* frostfs-aio https://git.frostfs.info/TrueCloudLab/frostfs-aio
|
||||
|
||||
## Set Up
|
||||
|
||||
1. Run frostfs-aio https://git.frostfs.info/TrueCloudLab/frostfs-aio/src/branch/nightly-v1.7 (branch `nightly-v1.7`)
|
||||
using `make up`
|
||||
2. Run `make refill` to get gas to `/path/to/frostfs-aio/wallets/wallet1.json` wallet (this wallet be used as backend
|
||||
wallet)
|
||||
3. Compile and deploy `NYAN` NFT token contract:
|
||||
|
||||
```shell
|
||||
$ neo-go contract compile -i nyan/contract.go -o nyan/contract.nef -m nyan/contract.manifest.json -c nyan/contract.yml
|
||||
$ neo-go contract deploy -i nyan/contract.nef -m nyan/contract.manifest.json -r http://localhost:30333 -w /path/to/frostfs-aio/wallets/wallet1.json [ NhCHDEtGgSph1v6PmjFC1gtzJWNKtNSadk ]
|
||||
...
|
||||
Contract: f167e80e620a70b47cd6f8c8e37229107dfab340
|
||||
```
|
||||
|
||||
3. Create public-read-write container:
|
||||
|
||||
```shell
|
||||
$ make cred
|
||||
|
||||
# create user-cli-cfg.yaml file with the following content
|
||||
$ cat user-cli-cfg.yaml
|
||||
wallet: /config/user-wallet.json
|
||||
password: ""
|
||||
rpc-endpoint: localhost:8080
|
||||
|
||||
$ docker cp user-cli-cfg.yaml aio:/config/user-cli-cfg.yaml
|
||||
$ docker exec aio frostfs-cli container create -c /config/user-cli-cfg.yaml --policy 'REP 1' --await
|
||||
CID: 2aK1oHPovFPoqQwf6A3DPjwPBYUKmEqhWE81omGauNTH
|
||||
awaiting...
|
||||
container has been persisted on sidechain
|
||||
|
||||
$ docker exec aio frostfs-cli -c /config/user-cli-cfg.yaml ape-manager add --chain-id nyan --rule 'Allow Object.* *' --target-type container --target-name 2aK1oHPovFPoqQwf6A3DPjwPBYUKmEqhWE81omGauNTH
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
![HighLevelArchitecture](seq.svg)
|
||||
|
||||
More about notary request: https://github.com/nspcc-dev/neo-go/blob/master/docs/notary.md
|
||||
|
||||
## Run server
|
||||
|
||||
Update `backend/config.yml` with contract hash and container id from previous steps
|
||||
|
||||
```yaml
|
||||
wallet: "/path/to/frostfs-aio/wallets/wallet1.json"
|
||||
password: ""
|
||||
rpc_endpoint: "http://localhost:30333"
|
||||
rpc_endpoint_ws: "ws://localhost:30333/ws"
|
||||
nyan_contract: "f167e80e620a70b47cd6f8c8e37229107dfab340"
|
||||
storage_node: "localhost:8080"
|
||||
storage_container: "2aK1oHPovFPoqQwf6A3DPjwPBYUKmEqhWE81omGauNTH"
|
||||
listen_address: ":5555"
|
||||
```
|
||||
|
||||
Run server:
|
||||
|
||||
```shell
|
||||
$ go run backend/main.go backend/config.yml
|
||||
2024-12-26T17:37:53.272+0300 INFO backend/main.go:315 start listening
|
||||
```
|
||||
|
||||
## Run client
|
||||
|
||||
Create brand-new wallet:
|
||||
|
||||
```shell
|
||||
$ neo-go wallet init -a -w wallet.json
|
||||
```
|
||||
|
||||
Update `client/config.yml`:
|
||||
|
||||
```yaml
|
||||
wallet: "wallet.json"
|
||||
password: ""
|
||||
rpc_endpoint: "http://localhost:30333"
|
||||
backend_key: "03b09baabff3f6107c7e9acb8721a6fc5618d45b50247a314d82e548702cce8cd5"
|
||||
nyan_contract: "f167e80e620a70b47cd6f8c8e37229107dfab340"
|
||||
backend_url: "http://localhost:5555"
|
||||
```
|
||||
|
||||
Run client:
|
||||
|
||||
```shell
|
||||
$ go run client/main.go client/config.yml
|
||||
Notarize sending: mainHash - a5ebe7957e54a18fa433fe497b50e11b656a1bdd4595965ba21af49027d2ce0e, fallbackHash - 151be0a2fc3bd39819a2fa95f105532a93723c82112bd188ed735edc5cc3cf9a, vub - 1818
|
||||
new token id 9db58027be52d270e7ee57385c8731d8eebc6c6deb71e2c8ea11ddc5bd588273
|
||||
```
|
||||
|
||||
Now you can get nft properties:
|
||||
|
||||
```shell
|
||||
$ curl http://localhost:5555/properties/9db58027be52d270e7ee57385c8731d8eebc6c6deb71e2c8ea11ddc5bd588273 | jq
|
||||
{
|
||||
"address": "2aK1oHPovFPoqQwf6A3DPjwPBYUKmEqhWE81omGauNTH/DdVQ1rhjeHfLqFdNbhW56RGkzJQnDUPsdZ9jVpMjCcCk",
|
||||
"id": "9db58027be52d270e7ee57385c8731d8eebc6c6deb71e2c8ea11ddc5bd588273",
|
||||
"name": "404.gif",
|
||||
"owner": "NL29YAWjsuj3JUFGgsekhyRTKUYTDqXg2G"
|
||||
}
|
||||
```
|
||||
|
||||
You also can get gif using frostfs-http-gw, just use the url with address from previous
|
||||
step `http://localhost:8081/get/<address>`:
|
||||
http://localhost:8081/get/2aK1oHPovFPoqQwf6A3DPjwPBYUKmEqhWE81omGauNTH/DdVQ1rhjeHfLqFdNbhW56RGkzJQnDUPsdZ9jVpMjCcCk
|
||||
|
8
app/backend/config.yml
Normal file
8
app/backend/config.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
wallet: "/backend/wallet.json"
|
||||
password: ""
|
||||
rpc_endpoint: "http://localhost:30333"
|
||||
rpc_endpoint_ws: "ws://localhost:30333/ws"
|
||||
nyan_contract: "f167e80e620a70b47cd6f8c8e37229107dfab340"
|
||||
storage_node: "localhost:8080"
|
||||
storage_container: "2aK1oHPovFPoqQwf6A3DPjwPBYUKmEqhWE81omGauNTH"
|
||||
listen_address: ":5555"
|
670
app/backend/main.go
Normal file
670
app/backend/main.go
Normal file
|
@ -0,0 +1,670 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/elliptic"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
morphclient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/subscriber"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
cfgRPCEndpoint = "rpc_endpoint"
|
||||
cfgRPCEndpointWS = "rpc_endpoint_ws"
|
||||
cfgWallet = "wallet"
|
||||
cfgPassword = "password"
|
||||
cfgNyanContract = "nyan_contract"
|
||||
cfgStorageNode = "storage_node"
|
||||
cfgStorageContainer = "storage_container"
|
||||
cfgListenAddress = "listen_address"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
if len(os.Args) != 2 {
|
||||
die(fmt.Errorf("invalid args: %v", os.Args))
|
||||
}
|
||||
|
||||
viper.GetViper().SetConfigType("yml")
|
||||
|
||||
f, err := os.Open(os.Args[1])
|
||||
die(err)
|
||||
die(viper.GetViper().ReadConfig(f))
|
||||
die(f.Close())
|
||||
|
||||
s, err := NewServer(ctx)
|
||||
die(err)
|
||||
|
||||
die(s.Listen(ctx))
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
p *pool.Pool
|
||||
acc *wallet.Account
|
||||
act *actor.Actor
|
||||
gasAct *nep17.Token
|
||||
nyanHash util.Uint160
|
||||
cnrID cid.ID
|
||||
log *zap.Logger
|
||||
rpcCli *rpcclient.Client
|
||||
sub subscriber.Subscriber
|
||||
}
|
||||
|
||||
func NewServer(ctx context.Context) (*Server, error) {
|
||||
rpcCli, err := rpcclient.New(ctx, viper.GetString(cfgRPCEndpoint), rpcclient.Options{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := wallet.NewWalletFromFile(viper.GetString(cfgWallet))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acc := w.GetAccount(w.GetChangeAddress())
|
||||
if err = acc.Decrypt(viper.GetString(cfgPassword), w.Scrypt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
act, err := actor.NewSimple(rpcCli, acc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := createPool(ctx, acc, viper.GetString(cfgStorageNode))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contractNyanHash, err := util.Uint160DecodeStringLE(viper.GetString(cfgNyanContract))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cnrID cid.ID
|
||||
if err = cnrID.DecodeString(viper.GetString(cfgStorageContainer)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
neoClient, err := morphclient.New(ctx, acc.PrivateKey(),
|
||||
morphclient.WithEndpoints(morphclient.Endpoint{Address: viper.GetString(cfgRPCEndpointWS), Priority: 1}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new morph client: %w", err)
|
||||
}
|
||||
|
||||
if err = neoClient.EnableNotarySupport(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params := new(subscriber.Params)
|
||||
params.Client = neoClient
|
||||
l, err := logger.NewLogger(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params.Log = l
|
||||
sub, err := subscriber.New(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = sub.SubscribeForNotaryRequests(acc.ScriptHash()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log, err := zap.NewDevelopment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Server{
|
||||
p: p,
|
||||
acc: acc,
|
||||
act: act,
|
||||
rpcCli: rpcCli,
|
||||
nyanHash: contractNyanHash,
|
||||
gasAct: nep17.New(act, gas.Hash),
|
||||
cnrID: cnrID,
|
||||
log: log,
|
||||
sub: sub,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) Listen(ctx context.Context) error {
|
||||
if err := s.notaryDeposit(s.acc.ScriptHash()); err != nil {
|
||||
return fmt.Errorf("notary backend deposit: %w", err)
|
||||
}
|
||||
|
||||
go s.runNotaryValidator(ctx)
|
||||
|
||||
http.DefaultServeMux.HandleFunc("/balance", func(w http.ResponseWriter, r *http.Request) {
|
||||
s.log.Info("balance request")
|
||||
|
||||
res, err := s.gasAct.BalanceOf(s.acc.ScriptHash())
|
||||
if err != nil {
|
||||
s.log.Error("balance error", zap.Error(err))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if _, err = w.Write([]byte(strconv.FormatInt(res.Int64(), 10))); err != nil {
|
||||
s.log.Error("write response error", zap.Error(err))
|
||||
}
|
||||
})
|
||||
|
||||
http.DefaultServeMux.HandleFunc("/properties/{tokenID}", func(w http.ResponseWriter, r *http.Request) {
|
||||
s.log.Info("properties request")
|
||||
|
||||
tokenIDStr := r.PathValue("tokenID")
|
||||
tokenID, err := hex.DecodeString(tokenIDStr)
|
||||
if err != nil {
|
||||
s.log.Error("invalid token ID", zap.String("tokenID", tokenIDStr), zap.Error(err))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
m, err := unwrap.Map(s.act.Call(s.nyanHash, "properties", tokenID))
|
||||
if err != nil {
|
||||
s.log.Error("call properties", zap.String("tokenID", tokenIDStr), zap.Error(err))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
props, err := parseMap(m)
|
||||
if err != nil {
|
||||
s.log.Error("parse properties", zap.String("tokenID", tokenIDStr), zap.Error(err))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := json.Marshal(props)
|
||||
if err != nil {
|
||||
s.log.Error("parse properties", zap.String("tokenID", tokenIDStr), zap.Error(err))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if _, err = w.Write(data); err != nil {
|
||||
s.log.Error("write response error", zap.Error(err))
|
||||
}
|
||||
})
|
||||
|
||||
http.DefaultServeMux.HandleFunc("/notary-deposit/{userAddress}", func(w http.ResponseWriter, r *http.Request) {
|
||||
s.log.Info("notary-deposit request", zap.String("url", r.URL.String()))
|
||||
|
||||
var userID user.ID
|
||||
err := userID.DecodeString(r.PathValue("userAddress"))
|
||||
if err != nil {
|
||||
s.log.Error("invalid user address", zap.String("address", r.PathValue("userAddress")), zap.Error(err))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
sh, err := userID.ScriptHash()
|
||||
if err != nil {
|
||||
s.log.Error("invalid user script hash", zap.Error(err))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.notaryDeposit(sh); err != nil {
|
||||
s.log.Error("failed to notary deposit", zap.Error(err))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
return http.ListenAndServe(viper.GetString(cfgListenAddress), nil)
|
||||
}
|
||||
|
||||
func parseMap(m *stackitem.Map) (map[string]string, error) {
|
||||
items := m.Value().([]stackitem.MapElement)
|
||||
res := make(map[string]string)
|
||||
|
||||
for _, item := range items {
|
||||
k, err := item.Key.TryBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := item.Value.TryBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kStr := string(k)
|
||||
|
||||
switch kStr {
|
||||
case "id":
|
||||
res[kStr] = hex.EncodeToString(v)
|
||||
default:
|
||||
res[kStr] = string(v)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func createPool(ctx context.Context, acc *wallet.Account, addr string) (*pool.Pool, error) {
|
||||
var prm pool.InitParameters
|
||||
prm.SetKey(&acc.PrivateKey().PrivateKey)
|
||||
prm.AddNode(pool.NewNodeParam(1, addr, 1))
|
||||
prm.SetNodeDialTimeout(5 * time.Second)
|
||||
|
||||
p, err := pool.NewPool(prm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new Pool: %w", err)
|
||||
}
|
||||
|
||||
err = p.Dial(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dial: %w", err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (s *Server) runNotaryValidator(ctx context.Context) {
|
||||
s.log.Info("start listening")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
die(ctx.Err())
|
||||
case notaryEvent, ok := <-s.sub.NotificationChannels().NotaryRequestsCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
s.log.Info("notary request", zap.String("hash", notaryEvent.NotaryRequest.Hash().String()),
|
||||
zap.String("main", notaryEvent.NotaryRequest.MainTransaction.Hash().String()),
|
||||
zap.String("fb", notaryEvent.NotaryRequest.FallbackTransaction.Hash().String()))
|
||||
|
||||
switch notaryEvent.Type {
|
||||
case mempoolevent.TransactionAdded:
|
||||
tokenName, err := s.parseNotaryEvent(notaryEvent)
|
||||
if err != nil {
|
||||
s.log.Error("parse notary event", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
nAct := s.notaryActor(notaryEvent.NotaryRequest.MainTransaction.Scripts[1])
|
||||
|
||||
isMain, err := s.checkNotaryRequest(nAct, tokenName)
|
||||
if err != nil {
|
||||
s.log.Error("check notary request", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if isMain {
|
||||
err = s.proceedMainTx(ctx, nAct, notaryEvent, tokenName)
|
||||
} else {
|
||||
err = s.proceedFbTx(ctx, nAct, notaryEvent, tokenName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.log.Error("proceed notary tx", zap.Bool("main", isMain), zap.String("token", tokenName), zap.Error(err))
|
||||
} else {
|
||||
s.log.Info("proceed notary tx", zap.Bool("main", isMain), zap.String("token", tokenName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) parseNotaryEvent(notaryEvent *result.NotaryRequestEvent) (string, error) {
|
||||
if len(notaryEvent.NotaryRequest.MainTransaction.Signers) != 3 {
|
||||
return "", errors.New("error not enough signers")
|
||||
}
|
||||
|
||||
if notaryEvent.NotaryRequest.Witness.ScriptHash().Equals(s.acc.ScriptHash()) {
|
||||
return "", fmt.Errorf("ignore owned notary request: %s", notaryEvent.NotaryRequest.Hash().String())
|
||||
}
|
||||
|
||||
_, tokenName, err := validateNotaryRequest(notaryEvent.NotaryRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenName, err
|
||||
}
|
||||
|
||||
func (s *Server) notaryActor(userWitness transaction.Witness) *notary.Actor {
|
||||
pubBytes, ok := vm.ParseSignatureContract(userWitness.VerificationScript)
|
||||
if !ok {
|
||||
die(errors.New("invalid verification script"))
|
||||
}
|
||||
pub, err := keys.NewPublicKeyFromBytes(pubBytes, elliptic.P256())
|
||||
die(err)
|
||||
userAcc := notary.FakeSimpleAccount(pub)
|
||||
|
||||
coSigners := []actor.SignerAccount{
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: s.acc.ScriptHash(),
|
||||
Scopes: transaction.None,
|
||||
},
|
||||
Account: s.acc,
|
||||
},
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: userAcc.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: userAcc,
|
||||
},
|
||||
}
|
||||
|
||||
nAct, err := notary.NewActor(s.rpcCli, coSigners, s.acc)
|
||||
die(err)
|
||||
|
||||
return nAct
|
||||
}
|
||||
|
||||
func (s *Server) notaryDeposit(to util.Uint160) error {
|
||||
data := []any{to, int64(math.MaxUint32)}
|
||||
_, err := s.act.Wait(s.gasAct.Transfer(s.act.Sender(), notary.Hash, big.NewInt(1*native.GASFactor), data))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) checkNotaryRequest(nAct *notary.Actor, tokenName string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *Server) proceedMainTx(ctx context.Context, nAct *notary.Actor, notaryEvent *result.NotaryRequestEvent, tokenName string) error {
|
||||
err := nAct.Sign(notaryEvent.NotaryRequest.MainTransaction)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sign: %w", err)
|
||||
}
|
||||
|
||||
mainHash, fallbackHash, vub, err := nAct.Notarize(notaryEvent.NotaryRequest.MainTransaction, nil)
|
||||
s.log.Info("notarize sending",
|
||||
zap.String("hash", notaryEvent.NotaryRequest.Hash().String()),
|
||||
zap.String("main", mainHash.String()), zap.String("fb", fallbackHash.String()),
|
||||
zap.Uint32("vub", vub))
|
||||
|
||||
_, err = nAct.Wait(mainHash, fallbackHash, vub, err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait: %w", err)
|
||||
}
|
||||
|
||||
url := "https://www.nyan.cat/cats/" + tokenName
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get url '%s' : %w", url, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
s.log.Error("close response bode", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
var ownerID user.ID
|
||||
user.IDFromKey(&ownerID, s.acc.PrivateKey().PrivateKey.PublicKey)
|
||||
|
||||
obj := object.New()
|
||||
obj.SetContainerID(s.cnrID)
|
||||
obj.SetOwnerID(ownerID)
|
||||
|
||||
var prm pool.PrmObjectPut
|
||||
prm.SetPayload(resp.Body)
|
||||
prm.SetHeader(*obj)
|
||||
|
||||
objID, err := s.p.PutObject(ctx, prm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("put object '%s': %w", url, err)
|
||||
}
|
||||
|
||||
addr := s.cnrID.EncodeToString() + "/" + objID.ObjectID.EncodeToString()
|
||||
s.log.Info("put object", zap.String("url", url), zap.String("address", addr))
|
||||
|
||||
_, err = s.act.Wait(s.act.SendCall(s.nyanHash, "setAddress", tokenName, addr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait setAddress: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) proceedFbTx(ctx context.Context, nAct *notary.Actor, notaryEvent *result.NotaryRequestEvent, tokenName string) error {
|
||||
err := nAct.Sign(notaryEvent.NotaryRequest.FallbackTransaction)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sign: %w", err)
|
||||
}
|
||||
|
||||
_, err = nAct.Wait(nAct.Notarize(notaryEvent.NotaryRequest.FallbackTransaction, nil))
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Op is wrapper over Neo VM's opcode
|
||||
// and its parameter.
|
||||
type Op struct {
|
||||
code opcode.Opcode
|
||||
param []byte
|
||||
}
|
||||
|
||||
// Code returns Neo VM opcode.
|
||||
func (o Op) Code() opcode.Opcode {
|
||||
return o.code
|
||||
}
|
||||
|
||||
// Param returns parameter of wrapped
|
||||
// Neo VM opcode.
|
||||
func (o Op) Param() []byte {
|
||||
return o.param
|
||||
}
|
||||
|
||||
func validateNotaryRequest(req *payload.P2PNotaryRequest) (util.Uint160, string, error) {
|
||||
var (
|
||||
opCode opcode.Opcode
|
||||
param []byte
|
||||
)
|
||||
|
||||
ctx := vm.NewContext(req.MainTransaction.Script)
|
||||
ops := make([]Op, 0, 10) // 10 is maximum num of opcodes for calling contracts with 4 args(no arrays of arrays)
|
||||
|
||||
var err error
|
||||
for {
|
||||
opCode, param, err = ctx.Next()
|
||||
if err != nil {
|
||||
return util.Uint160{}, "", fmt.Errorf("could not get next opcode in script: %w", err)
|
||||
}
|
||||
|
||||
if opCode == opcode.RET {
|
||||
break
|
||||
}
|
||||
|
||||
ops = append(ops, Op{code: opCode, param: param})
|
||||
}
|
||||
|
||||
opsLen := len(ops)
|
||||
|
||||
contractSysCall := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(contractSysCall, interopnames.ToID([]byte(interopnames.SystemContractCall)))
|
||||
// check if it is tx with contract call
|
||||
if !bytes.Equal(ops[opsLen-1].param, contractSysCall) {
|
||||
return util.Uint160{}, "", errors.New("not contract syscall")
|
||||
}
|
||||
|
||||
// retrieve contract's script hash
|
||||
contractHash, err := util.Uint160DecodeBytesBE(ops[opsLen-2].param)
|
||||
if err != nil {
|
||||
return util.Uint160{}, "", err
|
||||
}
|
||||
|
||||
contractHashExpected, err := util.Uint160DecodeStringLE("f167e80e620a70b47cd6f8c8e37229107dfab340")
|
||||
if err != nil {
|
||||
return util.Uint160{}, "", err
|
||||
}
|
||||
|
||||
if !contractHash.Equals(contractHashExpected) {
|
||||
return util.Uint160{}, "", fmt.Errorf("unexpected contract hash: %s", contractHash)
|
||||
}
|
||||
|
||||
// retrieve contract's method
|
||||
contractMethod := string(ops[opsLen-3].param)
|
||||
if contractMethod != "mint" {
|
||||
return util.Uint160{}, "", fmt.Errorf("unexpecred contract method: %s", contractMethod)
|
||||
}
|
||||
|
||||
// check if there is a call flag(must be in range [0:15))
|
||||
callFlag := callflag.CallFlag(ops[opsLen-4].code - opcode.PUSH0)
|
||||
if callFlag > callflag.All {
|
||||
return util.Uint160{}, "", fmt.Errorf("incorrect call flag: %s", callFlag)
|
||||
}
|
||||
|
||||
args := ops[:opsLen-4]
|
||||
|
||||
if len(args) != 0 {
|
||||
err = validateParameterOpcodes(args)
|
||||
if err != nil {
|
||||
return util.Uint160{}, "", fmt.Errorf("could not validate arguments: %w", err)
|
||||
}
|
||||
|
||||
// without args packing opcodes
|
||||
args = args[:len(args)-2]
|
||||
}
|
||||
|
||||
if len(args) != 2 {
|
||||
return util.Uint160{}, "", fmt.Errorf("invalid param length: %d", len(args))
|
||||
}
|
||||
|
||||
sh, err := util.Uint160DecodeBytesBE(args[1].Param())
|
||||
|
||||
return sh, string(args[0].Param()), err
|
||||
}
|
||||
|
||||
// IntFromOpcode tries to retrieve int from Op.
|
||||
func IntFromOpcode(op Op) (int64, error) {
|
||||
switch code := op.Code(); {
|
||||
case code == opcode.PUSHM1:
|
||||
return -1, nil
|
||||
case code >= opcode.PUSH0 && code <= opcode.PUSH16:
|
||||
return int64(code - opcode.PUSH0), nil
|
||||
case code <= opcode.PUSHINT256:
|
||||
return bigint.FromBytes(op.Param()).Int64(), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unexpected INT opcode %s", code)
|
||||
}
|
||||
}
|
||||
|
||||
func validateParameterOpcodes(ops []Op) error {
|
||||
l := len(ops)
|
||||
|
||||
if ops[l-1].code != opcode.PACK {
|
||||
return fmt.Errorf("unexpected packing opcode: %s", ops[l-1].code)
|
||||
}
|
||||
|
||||
argsLen, err := IntFromOpcode(ops[l-2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse argument len: %w", err)
|
||||
}
|
||||
|
||||
err = validateNestedArgs(argsLen, ops[:l-2])
|
||||
return err
|
||||
}
|
||||
|
||||
func validateNestedArgs(expArgLen int64, ops []Op) error {
|
||||
var (
|
||||
currentCode opcode.Opcode
|
||||
|
||||
opsLenGot = len(ops)
|
||||
)
|
||||
|
||||
for i := opsLenGot - 1; i >= 0; i-- {
|
||||
// only PUSH(also, PACK for arrays and CONVERT for booleans)
|
||||
// codes are allowed; number of params and their content must
|
||||
// be checked in a notary parser and a notary handler of a
|
||||
// particular contract
|
||||
switch currentCode = ops[i].code; {
|
||||
case currentCode <= opcode.PUSH16:
|
||||
case currentCode == opcode.CONVERT:
|
||||
if i == 0 || ops[i-1].code != opcode.PUSHT && ops[i-1].code != opcode.PUSHF {
|
||||
return errors.New("errUnexpectedCONVERT")
|
||||
}
|
||||
|
||||
expArgLen++
|
||||
case currentCode == opcode.PACK:
|
||||
if i == 0 {
|
||||
return errors.New("errIncorrectArgPacking")
|
||||
}
|
||||
|
||||
argsLen, err := IntFromOpcode(ops[i-1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse argument len: %w", err)
|
||||
}
|
||||
|
||||
expArgLen += argsLen + 1
|
||||
i--
|
||||
default:
|
||||
return fmt.Errorf("received main tx has unexpected(not PUSH) NeoVM opcode: %s", currentCode)
|
||||
}
|
||||
}
|
||||
|
||||
if int64(opsLenGot) != expArgLen {
|
||||
return errors.New("errIncorrectArgPacking")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func die(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
debug.PrintStack()
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
6
app/client/config.yml
Normal file
6
app/client/config.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
wallet: "/user/wallet.json"
|
||||
password: ""
|
||||
rpc_endpoint: "http://localhost:30333"
|
||||
backend_key: "03b09baabff3f6107c7e9acb8721a6fc5618d45b50247a314d82e548702cce8cd5"
|
||||
nyan_contract: "f167e80e620a70b47cd6f8c8e37229107dfab340"
|
||||
backend_url: "http://localhost:5555"
|
251
app/client/main.go
Normal file
251
app/client/main.go
Normal file
|
@ -0,0 +1,251 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/hrw"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
cfgRPCEndpoint = "rpc_endpoint"
|
||||
cfgBackendKey = "backend_key"
|
||||
cfgWallet = "wallet"
|
||||
cfgPassword = "password"
|
||||
cfgNyanContract = "nyan_contract"
|
||||
cfgBackendURL = "backend_url"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
if len(os.Args) != 2 {
|
||||
die(fmt.Errorf("invalid args: %v", os.Args))
|
||||
}
|
||||
|
||||
viper.GetViper().SetConfigType("yml")
|
||||
|
||||
f, err := os.Open(os.Args[1])
|
||||
die(err)
|
||||
die(viper.GetViper().ReadConfig(f))
|
||||
die(f.Close())
|
||||
|
||||
rpcCli, err := rpcclient.New(ctx, viper.GetString(cfgRPCEndpoint), rpcclient.Options{})
|
||||
die(err)
|
||||
|
||||
backendKey, err := keys.NewPublicKeyFromString(viper.GetString(cfgBackendKey))
|
||||
die(err)
|
||||
|
||||
w, err := wallet.NewWalletFromFile(viper.GetString(cfgWallet))
|
||||
die(err)
|
||||
acc := w.GetAccount(w.GetChangeAddress())
|
||||
err = acc.Decrypt(viper.GetString(cfgPassword), w.Scrypt)
|
||||
die(err)
|
||||
|
||||
contractHash, err := util.Uint160DecodeStringLE(viper.GetString(cfgNyanContract))
|
||||
die(err)
|
||||
|
||||
die(claimNotaryDeposit(acc))
|
||||
die(makeNotaryRequest(backendKey, acc, rpcCli, contractHash))
|
||||
}
|
||||
|
||||
func claimNotaryDeposit(acc *wallet.Account) error {
|
||||
resp, err := http.Get(viper.GetString(cfgBackendURL) + "/notary-deposit/" + acc.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("notary deposit failed: %d, %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeNotaryRequest(backendKey *keys.PublicKey, acc *wallet.Account, rpcCli *rpcclient.Client, contractHash util.Uint160) error {
|
||||
coSigners := []actor.SignerAccount{
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: backendKey.GetScriptHash(),
|
||||
Scopes: transaction.None,
|
||||
},
|
||||
Account: notary.FakeSimpleAccount(backendKey),
|
||||
},
|
||||
{
|
||||
Signer: transaction.Signer{
|
||||
Account: acc.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: acc,
|
||||
},
|
||||
}
|
||||
|
||||
nyanCat, err := getFreeNyanCat(rpcCli, acc, contractHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get free cat: %w", err)
|
||||
}
|
||||
|
||||
nAct, err := notary.NewActor(rpcCli, coSigners, acc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := nAct.MakeTunedCall(contractHash, "mint", nil, nil, acc.ScriptHash(), nyanCat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mainHash, fallbackHash, vub, err := nAct.Notarize(tx, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Notarize sending: mainHash - %v, fallbackHash - %v, vub - %d\n", mainHash, fallbackHash, vub)
|
||||
|
||||
res, err := nAct.Wait(mainHash, fallbackHash, vub, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.VMState != vmstate.Halt {
|
||||
return fmt.Errorf("invalid vm state: %s", res.VMState)
|
||||
}
|
||||
|
||||
if len(res.Stack) != 1 {
|
||||
return fmt.Errorf("invalid stack size: %d", len(res.Stack))
|
||||
}
|
||||
|
||||
tokenID, err := res.Stack[0].TryBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("new token id", hex.EncodeToString(tokenID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var listOfCats = []string{
|
||||
"404.gif",
|
||||
"america.gif",
|
||||
"balloon.gif",
|
||||
"bday.gif",
|
||||
"bloon.gif",
|
||||
"breakfast.gif",
|
||||
"daft.gif",
|
||||
"dub.gif",
|
||||
"easter.gif",
|
||||
"elevator.gif",
|
||||
"fat.gif",
|
||||
"fiesta.gif",
|
||||
"floppy.gif",
|
||||
"ganja.gif",
|
||||
"gb.gif",
|
||||
"grumpy.gif",
|
||||
"j5.gif",
|
||||
"jacksnyan.gif",
|
||||
"jamaicnyan.gif",
|
||||
"jazz.gif",
|
||||
"jazzcat.gif",
|
||||
"manyan.gif",
|
||||
"melon.gif",
|
||||
"mexinyan.gif",
|
||||
"mummy.gif",
|
||||
"newyear.gif",
|
||||
"nyanamerica.gif",
|
||||
"nyancat.gif",
|
||||
"nyancoin.gif",
|
||||
"nyandoge.gif",
|
||||
"nyaninja.gif",
|
||||
"nyanvirus.gif",
|
||||
"oldnewyear.gif",
|
||||
"oldnyan.gif",
|
||||
"original.gif",
|
||||
"paddy.gif",
|
||||
"pikanyan.gif",
|
||||
"pirate.gif",
|
||||
"pumpkin.gif",
|
||||
"rasta.gif",
|
||||
"retro.gif",
|
||||
"sad.gif",
|
||||
"sadnyan.gif",
|
||||
"skrillex.gif",
|
||||
"slomo.gif",
|
||||
"slomocat.gif",
|
||||
"smooth.gif",
|
||||
"smurfcat.gif",
|
||||
"star.gif",
|
||||
"starsheep.gif",
|
||||
"tacnayn.gif",
|
||||
"tacodog.gif",
|
||||
"technyancolor.gif",
|
||||
"toaster.gif",
|
||||
"vday.gif",
|
||||
"watermelon.gif",
|
||||
"wtf.gif",
|
||||
"xmas.gif",
|
||||
"xmasold.gif",
|
||||
"zombie.gif",
|
||||
}
|
||||
|
||||
func getFreeNyanCat(cli *rpcclient.Client, acc *wallet.Account, contractHash util.Uint160) (string, error) {
|
||||
indexes := make([]uint64, len(listOfCats))
|
||||
for i := range indexes {
|
||||
indexes[i] = uint64(i)
|
||||
}
|
||||
|
||||
act, err := actor.NewSimple(cli, acc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h := hrw.Hash(acc.ScriptHash().BytesBE())
|
||||
hrw.Sort(indexes, h)
|
||||
|
||||
var cat string
|
||||
for _, index := range indexes {
|
||||
cat = listOfCats[index]
|
||||
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(cat))
|
||||
tokenID := hash.Sum(nil)
|
||||
|
||||
if _, err := unwrap.Uint160(act.Call(contractHash, "ownerOf", tokenID)); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if cat == "" {
|
||||
return "", errors.New("all cats are taken")
|
||||
}
|
||||
|
||||
return cat, nil
|
||||
}
|
||||
|
||||
func die(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
debug.PrintStack()
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
80
app/go.mod
Normal file
80
app/go.mod
Normal file
|
@ -0,0 +1,80 @@
|
|||
module contract
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-node v0.44.4
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20241206094944-81c423e7094d
|
||||
github.com/nspcc-dev/neo-go v0.106.3
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec
|
||||
github.com/spf13/viper v1.19.0
|
||||
go.uber.org/zap v1.27.0
|
||||
)
|
||||
|
||||
replace github.com/nspcc-dev/neo-go => git.frostfs.info/TrueCloudLab/neoneo-go v0.106.1-0.20241015133823-8aee80dbdc07
|
||||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.21.0-rc.4 // indirect
|
||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88 // indirect
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1 // indirect
|
||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
||||
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02 // indirect
|
||||
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.14.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/consensys/bavard v0.1.13 // indirect
|
||||
github.com/consensys/gnark-crypto v0.14.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/holiman/uint256 v1.3.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mmcloughlin/addchain v0.4.0 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20240830112754-291b000d1f3b // indirect
|
||||
github.com/nspcc-dev/rfc6979 v0.2.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/prometheus/client_golang v1.20.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/ssgreg/journald v1.0.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
github.com/twmb/murmur3 v1.1.8 // indirect
|
||||
go.etcd.io/bbolt v1.3.11 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/grpc v1.66.2 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
rsc.io/tmplfunc v0.0.3 // indirect
|
||||
)
|
292
app/go.sum
Normal file
292
app/go.sum
Normal file
|
@ -0,0 +1,292 @@
|
|||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.21.0-rc.4 h1:o3iqVmbvFsfe8kpB2Hvuix6Q/tAhbiPLP91xK4lmoBQ=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.21.0-rc.4/go.mod h1:5fSm/l5xSjGWqsPUffSdboiGFUHa7y/1S0fvxzQowN8=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-node v0.44.4 h1:ul6/DMWRbd55dfoyMXUBhx0SS1d5mhJbnUTMVL4Uc08=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-node v0.44.4/go.mod h1:LNEeZQ7Nc8r5wImauf/R9/bxPaDAuGnOk9kspUz4o0I=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88 h1:9bvBDLApbbO5sXBKdODpE9tzy3HV99nXxkDWNn22rdI=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88/go.mod h1:kbwB4v2o6RyOfCo9kEFeUDZIX3LKhmS0yXPrtvzkQ1g=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20241206094944-81c423e7094d h1:FpXI+mOrmJk3t2MKQFZuhLjCHDyDeo5rtP1WXl7gUWc=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20241206094944-81c423e7094d/go.mod h1:eoK7+KZQ9GJxbzIs6vTnoUJqFDppavInLRHaN4MYgZg=
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
|
||||
git.frostfs.info/TrueCloudLab/neoneo-go v0.106.1-0.20241015133823-8aee80dbdc07 h1:gPaqGsk6gSWQyNVjaStydfUz6Z/loHc9XyvGrJ5qSPY=
|
||||
git.frostfs.info/TrueCloudLab/neoneo-go v0.106.1-0.20241015133823-8aee80dbdc07/go.mod h1:bZyJexBlrja4ngxiBgo8by5pVHuAbhg9l09/8yVGDyg=
|
||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA=
|
||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc=
|
||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA=
|
||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0/go.mod h1:dhY+oy274hV8wGvGL4MwwMpdL3GYvaX1a8GQZQHvlF8=
|
||||
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02 h1:HeY8n27VyPRQe49l/fzyVMkWEB2fsLJYKp64pwA7tz4=
|
||||
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02/go.mod h1:rQFJJdEOV7KbbMtQYR2lNfiZk+ONRDJSbMCTWxKt8Fw=
|
||||
github.com/VictoriaMetrics/easyproto v0.1.4 h1:r8cNvo8o6sR4QShBXQd1bKw/VVLSQma/V2KhTBPf+Sc=
|
||||
github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.14.2 h1:YXVoyPndbdvcEVcseEovVfp0qjJp7S+i5+xgp/Nfbdc=
|
||||
github.com/bits-and-blooms/bitset v1.14.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
|
||||
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
|
||||
github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E=
|
||||
github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs=
|
||||
github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
|
||||
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
|
||||
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
|
||||
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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20240830112754-291b000d1f3b h1:DRG4cRqIOmI/nUPggMgR92Jxt63Lxsuz40m5QpdvYXI=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20240830112754-291b000d1f3b/go.mod h1:d3cUseu4Asxfo9/QA/w4TtGjM0AbC9ynyab+PfH+Bso=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec h1:vDrbVXF2+2uP0RlkZmem3QYATcXCu9BzzGGCNsNcK7Q=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240727093519-1a48f1ce43ec/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
|
||||
github.com/nspcc-dev/rfc6979 v0.2.3 h1:QNVykGZ3XjFwM/88rGfV3oj4rKNBy+nYI6jM7q19hDI=
|
||||
github.com/nspcc-dev/rfc6979 v0.2.3/go.mod h1:q3sCL1Ed7homjqYK8KmFSzEmm+7Ngyo7PePbZanhaDE=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
||||
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/ssgreg/journald v1.0.0 h1:0YmTDPJXxcWDPba12qNMdO6TxvfkFSYpFIJ31CwmLcU=
|
||||
github.com/ssgreg/journald v1.0.0/go.mod h1:RUckwmTM8ghGWPslq2+ZBZzbb9/2KgjzYZ4JEP+oRt0=
|
||||
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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
||||
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
|
||||
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
|
||||
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
|
||||
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
|
||||
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
|
||||
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
|
292
app/nyan/contract.go
Normal file
292
app/nyan/contract.go
Normal file
|
@ -0,0 +1,292 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||
)
|
||||
|
||||
// Prefixes used for contract data storage.
|
||||
const (
|
||||
balancePrefix = "b"
|
||||
accountPrefix = "a"
|
||||
tokenPrefix = "t"
|
||||
|
||||
ownerKey = 'o'
|
||||
totalSupplyKey = 's'
|
||||
)
|
||||
|
||||
type NFTItem struct {
|
||||
ID []byte
|
||||
Name string
|
||||
Owner interop.Hash160
|
||||
Address string
|
||||
}
|
||||
|
||||
func _deploy(data interface{}, isUpdate bool) {
|
||||
if isUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
args := data.(struct {
|
||||
Admin interop.Hash160
|
||||
})
|
||||
|
||||
if args.Admin == nil {
|
||||
panic("invalid admin")
|
||||
}
|
||||
|
||||
if len(args.Admin) != 20 {
|
||||
panic("invalid admin hash length")
|
||||
}
|
||||
|
||||
ctx := storage.GetContext()
|
||||
storage.Put(ctx, ownerKey, args.Admin)
|
||||
storage.Put(ctx, totalSupplyKey, 0)
|
||||
}
|
||||
|
||||
// Symbol returns token symbol, it's NYAN.
|
||||
func Symbol() string {
|
||||
return "NYAN"
|
||||
}
|
||||
|
||||
// Decimals returns token decimals, this NFT is non-divisible, so it's 0.
|
||||
func Decimals() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// TotalSupply is a contract method that returns the number of tokens minted.
|
||||
func TotalSupply() int {
|
||||
return storage.Get(storage.GetReadOnlyContext(), totalSupplyKey).(int)
|
||||
}
|
||||
|
||||
// BalanceOf returns the number of tokens owned by the specified address.
|
||||
func BalanceOf(holder interop.Hash160) int {
|
||||
if len(holder) != 20 {
|
||||
panic("bad owner address")
|
||||
}
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
return getBalanceOf(ctx, mkBalanceKey(holder))
|
||||
}
|
||||
|
||||
// OwnerOf returns the owner of the specified token.
|
||||
func OwnerOf(token []byte) interop.Hash160 {
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
return getNFT(ctx, token).Owner
|
||||
}
|
||||
|
||||
// Properties returns properties of the given NFT.
|
||||
func Properties(token []byte) map[string]string {
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
nft := getNFT(ctx, token)
|
||||
|
||||
result := map[string]string{
|
||||
"id": string(nft.ID),
|
||||
"owner": ownerAddress(nft.Owner),
|
||||
"name": nft.Name,
|
||||
"address": nft.Address,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Tokens returns an iterator that contains all the tokens minted by the contract.
|
||||
func Tokens() iterator.Iterator {
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
key := []byte(tokenPrefix)
|
||||
iter := storage.Find(ctx, key, storage.RemovePrefix|storage.KeysOnly)
|
||||
return iter
|
||||
}
|
||||
|
||||
func TokensList() []string {
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
key := []byte(tokenPrefix)
|
||||
iter := storage.Find(ctx, key, storage.RemovePrefix|storage.KeysOnly)
|
||||
keys := []string{}
|
||||
for iterator.Next(iter) {
|
||||
k := iterator.Value(iter)
|
||||
keys = append(keys, k.(string))
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// TokensOf returns an iterator with all tokens held by the specified address.
|
||||
func TokensOf(holder interop.Hash160) iterator.Iterator {
|
||||
if len(holder) != 20 {
|
||||
panic("bad owner address")
|
||||
}
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
key := mkAccountPrefix(holder)
|
||||
iter := storage.Find(ctx, key, storage.ValuesOnly)
|
||||
return iter
|
||||
}
|
||||
|
||||
func TokensOfList(holder interop.Hash160) [][]byte {
|
||||
if len(holder) != 20 {
|
||||
panic("bad owner address")
|
||||
}
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
key := mkAccountPrefix(holder)
|
||||
res := [][]byte{}
|
||||
iter := storage.Find(ctx, key, storage.ValuesOnly)
|
||||
for iterator.Next(iter) {
|
||||
res = append(res, iterator.Value(iter).([]byte))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Transfer token from its owner to another user, notice that it only has three
|
||||
// parameters because token owner can be deduced from token ID itself.
|
||||
func Transfer(to interop.Hash160, token []byte, data any) bool {
|
||||
if len(to) != 20 {
|
||||
panic("invalid 'to' address")
|
||||
}
|
||||
ctx := storage.GetContext()
|
||||
nft := getNFT(ctx, token)
|
||||
from := nft.Owner
|
||||
|
||||
if !runtime.CheckWitness(from) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !from.Equals(to) {
|
||||
nft.Owner = to
|
||||
setNFT(ctx, token, nft)
|
||||
|
||||
addToBalance(ctx, from, -1)
|
||||
removeToken(ctx, from, token)
|
||||
addToBalance(ctx, to, 1)
|
||||
addToken(ctx, to, token)
|
||||
}
|
||||
|
||||
postTransfer(from, to, token, data)
|
||||
return true
|
||||
}
|
||||
|
||||
func getNFT(ctx storage.Context, token []byte) NFTItem {
|
||||
key := mkTokenKey(token)
|
||||
val := storage.Get(ctx, key)
|
||||
if val == nil {
|
||||
panic("no token found")
|
||||
}
|
||||
|
||||
serializedNFT := val.([]byte)
|
||||
deserializedNFT := std.Deserialize(serializedNFT)
|
||||
return deserializedNFT.(NFTItem)
|
||||
}
|
||||
|
||||
func nftExists(ctx storage.Context, token []byte) bool {
|
||||
key := mkTokenKey(token)
|
||||
return storage.Get(ctx, key) != nil
|
||||
}
|
||||
|
||||
func setNFT(ctx storage.Context, token []byte, item NFTItem) {
|
||||
key := mkTokenKey(token)
|
||||
val := std.Serialize(item)
|
||||
storage.Put(ctx, key, val)
|
||||
}
|
||||
|
||||
// postTransfer emits Transfer event and calls onNEP11Payment if needed.
|
||||
func postTransfer(from interop.Hash160, to interop.Hash160, token []byte, data any) {
|
||||
runtime.Notify("Transfer", from, to, 1, token)
|
||||
if management.GetContract(to) != nil {
|
||||
contract.Call(to, "onNEP11Payment", contract.All, from, 1, token, data)
|
||||
}
|
||||
}
|
||||
|
||||
func Mint(user interop.Hash160, name string) []byte {
|
||||
ctx := storage.GetContext()
|
||||
tokenID := crypto.Sha256([]byte(name))
|
||||
if nftExists(ctx, tokenID) {
|
||||
panic("token already exists")
|
||||
}
|
||||
|
||||
nft := NFTItem{
|
||||
ID: tokenID,
|
||||
Owner: user,
|
||||
Name: name,
|
||||
}
|
||||
setNFT(ctx, tokenID, nft)
|
||||
addToBalance(ctx, user, 1)
|
||||
addToken(ctx, user, tokenID)
|
||||
|
||||
total := storage.Get(ctx, totalSupplyKey).(int) + 1
|
||||
storage.Put(ctx, totalSupplyKey, total)
|
||||
|
||||
return tokenID
|
||||
}
|
||||
|
||||
func SetAddress(name string, address string) {
|
||||
ctx := storage.GetContext()
|
||||
if !runtime.CheckWitness(storage.Get(ctx, ownerKey).(interop.Hash160)) {
|
||||
panic("not witnessed")
|
||||
}
|
||||
|
||||
tokenID := crypto.Sha256([]byte(name))
|
||||
nft := getNFT(ctx, tokenID)
|
||||
nft.Address = address
|
||||
setNFT(ctx, tokenID, nft)
|
||||
}
|
||||
|
||||
// mkAccountPrefix creates DB key-prefix for the account tokens specified
|
||||
// by concatenating accountPrefix and account address.
|
||||
func mkAccountPrefix(holder interop.Hash160) []byte {
|
||||
res := []byte(accountPrefix)
|
||||
return append(res, holder...)
|
||||
}
|
||||
|
||||
// mkBalanceKey creates DB key for the account specified by concatenating balancePrefix
|
||||
// and account address.
|
||||
func mkBalanceKey(holder interop.Hash160) []byte {
|
||||
res := []byte(balancePrefix)
|
||||
return append(res, holder...)
|
||||
}
|
||||
|
||||
// mkTokenKey creates DB key for the token specified by concatenating tokenPrefix
|
||||
// and token ID.
|
||||
func mkTokenKey(tokenID []byte) []byte {
|
||||
res := []byte(tokenPrefix)
|
||||
return append(res, tokenID...)
|
||||
}
|
||||
|
||||
// getBalanceOf returns the balance of an account using database key.
|
||||
func getBalanceOf(ctx storage.Context, balanceKey []byte) int {
|
||||
val := storage.Get(ctx, balanceKey)
|
||||
if val != nil {
|
||||
return val.(int)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// addToBalance adds an amount to the account balance. Amount can be negative.
|
||||
func addToBalance(ctx storage.Context, holder interop.Hash160, amount int) {
|
||||
key := mkBalanceKey(holder)
|
||||
old := getBalanceOf(ctx, key)
|
||||
old += amount
|
||||
if old > 0 {
|
||||
storage.Put(ctx, key, old)
|
||||
} else {
|
||||
storage.Delete(ctx, key)
|
||||
}
|
||||
}
|
||||
|
||||
// addToken adds a token to the account.
|
||||
func addToken(ctx storage.Context, holder interop.Hash160, token []byte) {
|
||||
key := mkAccountPrefix(holder)
|
||||
storage.Put(ctx, append(key, token...), token)
|
||||
}
|
||||
|
||||
// removeToken removes the token from the account.
|
||||
func removeToken(ctx storage.Context, holder interop.Hash160, token []byte) {
|
||||
key := mkAccountPrefix(holder)
|
||||
storage.Delete(ctx, append(key, token...))
|
||||
}
|
||||
|
||||
func ownerAddress(owner interop.Hash160) string {
|
||||
b := append([]byte{0x35}, owner...)
|
||||
return std.Base58CheckEncode(b)
|
||||
}
|
16
app/nyan/contract.yml
Normal file
16
app/nyan/contract.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
name: "NYAN NFT"
|
||||
supportedstandards: ["NEP-11"]
|
||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "tokens", "properties"]
|
||||
events:
|
||||
- name: Transfer
|
||||
parameters:
|
||||
- name: from
|
||||
type: Hash160
|
||||
- name: to
|
||||
type: Hash160
|
||||
- name: amount
|
||||
type: Integer
|
||||
- name: tokenId
|
||||
type: ByteArray
|
||||
permissions:
|
||||
- methods: ["onNEP11Payment"]
|
19
app/seq.puml
Normal file
19
app/seq.puml
Normal file
|
@ -0,0 +1,19 @@
|
|||
@startuml
|
||||
|
||||
actor user
|
||||
|
||||
user -> client: "Register" in system
|
||||
|
||||
client -> server: Claim notary deposit
|
||||
server -> blockhain: Add notary client deposit
|
||||
server -> blockhain: Add notary server deposit
|
||||
|
||||
client -> blockhain: Create notary request
|
||||
server <- blockhain: Listen notification
|
||||
server -> server: Validate and sign notary transaction request
|
||||
server -> blockhain: Send notary request
|
||||
blockhain -> blockhain: Check all signatures and accept transaction
|
||||
|
||||
user -> blockhain: Manage NFT
|
||||
|
||||
@enduml
|
29
app/seq.svg
Normal file
29
app/seq.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8 KiB |
Loading…
Reference in a new issue