mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-01-12 22:43:22 +08:00
Compare commits
14 Commits
v0.2.7
...
feature/at
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83cb5a9cfa | ||
|
|
9e11dd3c67 | ||
|
|
9b5caa589c | ||
|
|
0a77e3ff0f | ||
|
|
0ddcc8ea46 | ||
|
|
30bc624624 | ||
|
|
6672556383 | ||
|
|
73ac0ef6b5 | ||
|
|
2223bcf1d0 | ||
|
|
a75c87d09a | ||
|
|
d563ddbed2 | ||
|
|
80db77de6a | ||
|
|
62ec809af6 | ||
|
|
1aa358d504 |
@@ -2,7 +2,7 @@
|
||||
|
||||
# Gaze Indexer
|
||||
|
||||
Gaze Indexer is an open-source and modular indexing client for Bitcoin meta-protocols with **Unified Consistent APIs** across fungible token protocols.
|
||||
Gaze Indexer is an open-source and modular indexing client for Bitcoin meta-protocols. It has support for Runes out of the box, with **Unified Consistent APIs** across fungible token protocols.
|
||||
|
||||
Gaze Indexer is built with **modularity** in mind, allowing users to run all modules in one monolithic instance with a single command, or as a distributed cluster of micro-services.
|
||||
|
||||
|
||||
@@ -17,11 +17,12 @@ import (
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/indexer"
|
||||
"github.com/gaze-network/indexer-network/internal/config"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale"
|
||||
"github.com/gaze-network/indexer-network/modules/runes"
|
||||
"github.com/gaze-network/indexer-network/pkg/automaxprocs"
|
||||
"github.com/gaze-network/indexer-network/pkg/errorhandler"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
"github.com/gaze-network/indexer-network/pkg/middleware/errorhandler"
|
||||
"github.com/gaze-network/indexer-network/pkg/middleware/requestcontext"
|
||||
"github.com/gaze-network/indexer-network/pkg/middleware/requestlogger"
|
||||
"github.com/gaze-network/indexer-network/pkg/reportingclient"
|
||||
@@ -39,6 +40,7 @@ import (
|
||||
// Register Modules
|
||||
var Modules = do.Package(
|
||||
do.LazyNamed("runes", runes.New),
|
||||
do.LazyNamed("nodesale", nodesale.New),
|
||||
)
|
||||
|
||||
func NewRunCommand() *cobra.Command {
|
||||
@@ -136,16 +138,8 @@ func runHandler(cmd *cobra.Command, _ []string) error {
|
||||
// Initialize HTTP server
|
||||
do.Provide(injector, func(i do.Injector) (*fiber.App, error) {
|
||||
app := fiber.New(fiber.Config{
|
||||
AppName: "Gaze Indexer",
|
||||
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||
logger.ErrorContext(c.UserContext(), "Something went wrong, unhandled api error",
|
||||
slogx.String("event", "api_unhandled_error"),
|
||||
slogx.Error(err),
|
||||
)
|
||||
return errors.WithStack(c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Internal Server Error",
|
||||
}))
|
||||
},
|
||||
AppName: "Gaze Indexer",
|
||||
ErrorHandler: errorhandler.NewHTTPErrorHandler(),
|
||||
})
|
||||
app.
|
||||
Use(favicon.New()).
|
||||
@@ -164,7 +158,6 @@ func runHandler(cmd *cobra.Command, _ []string) error {
|
||||
logger.ErrorContext(c.UserContext(), "Something went wrong, panic in http handler", slogx.Any("panic", e), slog.String("stacktrace", string(buf)))
|
||||
},
|
||||
})).
|
||||
Use(errorhandler.New()).
|
||||
Use(compress.New(compress.Config{
|
||||
Level: compress.LevelDefault,
|
||||
}))
|
||||
|
||||
@@ -6,13 +6,15 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/constants"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale"
|
||||
"github.com/gaze-network/indexer-network/modules/runes"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var versions = map[string]string{
|
||||
"": constants.Version,
|
||||
"runes": runes.Version,
|
||||
"": constants.Version,
|
||||
"runes": runes.Version,
|
||||
"nodesale": nodesale.Version,
|
||||
}
|
||||
|
||||
type versionCmdOptions struct {
|
||||
|
||||
@@ -47,3 +47,10 @@ modules:
|
||||
password: "password"
|
||||
db_name: "postgres"
|
||||
# url: "postgres://postgres:password@localhost:5432/postgres?sslmode=prefer" # [Optional] This will override other database credentials above.
|
||||
nodesale:
|
||||
postgres:
|
||||
host: "localhost"
|
||||
port: 5432
|
||||
user: "postgres"
|
||||
password: "password"
|
||||
db_name: "postgres"
|
||||
|
||||
@@ -243,32 +243,39 @@ func (d *BitcoinNodeDatasource) prepareRange(fromHeight, toHeight int64) (start,
|
||||
}
|
||||
|
||||
// GetTransaction fetch transaction from Bitcoin node
|
||||
func (d *BitcoinNodeDatasource) GetRawTransactionAndHeightByTxHash(ctx context.Context, txHash chainhash.Hash) (*wire.MsgTx, int64, error) {
|
||||
func (d *BitcoinNodeDatasource) GetTransactionByHash(ctx context.Context, txHash chainhash.Hash) (*types.Transaction, error) {
|
||||
rawTxVerbose, err := d.btcclient.GetRawTransactionVerbose(&txHash)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to get raw transaction")
|
||||
return nil, errors.Wrap(err, "failed to get raw transaction")
|
||||
}
|
||||
|
||||
blockHash, err := chainhash.NewHashFromStr(rawTxVerbose.BlockHash)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to parse block hash")
|
||||
return nil, errors.Wrap(err, "failed to parse block hash")
|
||||
}
|
||||
block, err := d.btcclient.GetBlockVerbose(blockHash)
|
||||
block, err := d.btcclient.GetBlockVerboseTx(blockHash)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to get block header")
|
||||
return nil, errors.Wrap(err, "failed to get block header")
|
||||
}
|
||||
|
||||
// parse tx
|
||||
txBytes, err := hex.DecodeString(rawTxVerbose.Hex)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to decode transaction hex")
|
||||
return nil, errors.Wrap(err, "failed to decode transaction hex")
|
||||
}
|
||||
var msgTx wire.MsgTx
|
||||
if err := msgTx.Deserialize(bytes.NewReader(txBytes)); err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to deserialize transaction")
|
||||
return nil, errors.Wrap(err, "failed to deserialize transaction")
|
||||
}
|
||||
var txIndex uint32
|
||||
for i, tx := range block.Tx {
|
||||
if tx.Hex == rawTxVerbose.Hex {
|
||||
txIndex = uint32(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &msgTx, block.Height, nil
|
||||
return types.ParseMsgTx(&msgTx, block.Height, *blockHash, txIndex), nil
|
||||
}
|
||||
|
||||
// GetBlockHeader fetch block header from Bitcoin node
|
||||
|
||||
21
go.mod
21
go.mod
@@ -5,10 +5,11 @@ go 1.22
|
||||
require (
|
||||
github.com/Cleverse/go-utilities/utils v0.0.0-20240119201306-d71eb577ef11
|
||||
github.com/btcsuite/btcd v0.24.0
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3
|
||||
github.com/btcsuite/btcd/btcutil v1.1.5
|
||||
github.com/btcsuite/btcd/btcutil/psbt v1.1.9
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
||||
github.com/cockroachdb/errors v1.11.1
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
|
||||
github.com/gaze-network/uint128 v1.3.0
|
||||
github.com/gofiber/fiber/v2 v2.52.4
|
||||
github.com/golang-migrate/migrate/v4 v4.17.1
|
||||
@@ -21,24 +22,22 @@ require (
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/valyala/fasthttp v1.51.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sync v0.5.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/bitonicnl/verify-signed-message v0.7.1
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
||||
github.com/cockroachdb/redact v1.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/getsentry/sentry-go v0.18.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -76,10 +75,10 @@ require (
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.20.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
50
go.sum
50
go.sum
@@ -7,23 +7,18 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/bitonicnl/verify-signed-message v0.7.1 h1:1Qku9k9WgzobjqBY7tT3CLjWxtTJZxkYNhOV6QeCTjY=
|
||||
github.com/bitonicnl/verify-signed-message v0.7.1/go.mod h1:PR60twfJIaHEo9Wb6eJBh8nBHEZIQQx8CvRwh0YmEPk=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
|
||||
github.com/btcsuite/btcd v0.24.0 h1:gL3uHE/IaFj6fcZSu03SvqPMSx7s/dPzfpG/atRwWdo=
|
||||
github.com/btcsuite/btcd v0.24.0/go.mod h1:K4IDc1593s8jKXIF7yS7yCTSxrknB9z0STzc2j6XgE4=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
|
||||
github.com/btcsuite/btcd/btcutil/psbt v1.1.9 h1:UmfOIiWMZcVMOLaN+lxbbLSuoINGS1WmK1TZNI0b4yk=
|
||||
github.com/btcsuite/btcd/btcutil/psbt v1.1.9/go.mod h1:ehBEvU91lxSlXtA+zZz3iFYx7Yq9eqnKx4/kSrnsvMY=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
|
||||
@@ -55,12 +50,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
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/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/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/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg=
|
||||
github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA=
|
||||
@@ -103,8 +96,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||
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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
@@ -225,9 +218,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
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.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
@@ -251,14 +243,14 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf
|
||||
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.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
|
||||
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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=
|
||||
@@ -273,8 +265,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.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=
|
||||
@@ -287,19 +279,19 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
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=
|
||||
@@ -310,6 +302,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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=
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
nodesaleconfig "github.com/gaze-network/indexer-network/modules/nodesale/config"
|
||||
runesconfig "github.com/gaze-network/indexer-network/modules/runes/config"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
@@ -61,7 +62,8 @@ type BitcoinNodeClient struct {
|
||||
}
|
||||
|
||||
type Modules struct {
|
||||
Runes runesconfig.Config `mapstructure:"runes"`
|
||||
Runes runesconfig.Config `mapstructure:"runes"`
|
||||
Nodesale nodesaleconfig.Config `mapstructure:"nodesale"`
|
||||
}
|
||||
|
||||
type HTTPServerConfig struct {
|
||||
|
||||
196
modules/nodesale/api/httphandler/handlers.go
Normal file
196
modules/nodesale/api/httphandler/handlers.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/protobuf"
|
||||
repository "github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
repository *repository.Repository
|
||||
}
|
||||
|
||||
func New(repo *repository.Repository) *handler {
|
||||
h := handler{}
|
||||
h.repository = repo
|
||||
return &h
|
||||
}
|
||||
|
||||
func (h *handler) infoHandler(ctx *fiber.Ctx) error {
|
||||
block, err := h.repository.Queries.GetLastProcessedBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot get last processed block : %w", err)
|
||||
}
|
||||
err = ctx.JSON(infoResponse{
|
||||
IndexedBlockHeight: block.BlockHeight,
|
||||
IndexedBlockHash: block.BlockHash,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Go fiber cannot parse JSON: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) deployHandler(ctx *fiber.Ctx) error {
|
||||
deployId := ctx.Params("deployId")
|
||||
if deployId == "" {
|
||||
err := ctx.SendStatus(404)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Go fiber cannot send status: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var blockHeight, txIndex int32
|
||||
count, err := fmt.Sscanf(deployId, "%d-%d", &blockHeight, &txIndex)
|
||||
if count != 2 || err != nil {
|
||||
err := ctx.SendStatus(404)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Go fiber cannot send status: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
deploys, err := h.repository.Queries.GetNodesale(ctx.UserContext(), gen.GetNodesaleParams{
|
||||
BlockHeight: blockHeight,
|
||||
TxIndex: txIndex,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot get nodesale from db: %w", err)
|
||||
}
|
||||
if len(deploys) < 1 {
|
||||
err := ctx.SendStatus(404)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Go fiber cannot send status: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
deploy := deploys[0]
|
||||
|
||||
nodeCount, err := h.repository.Queries.GetNodeCountByTierIndex(ctx.UserContext(), gen.GetNodeCountByTierIndexParams{
|
||||
SaleBlock: deploy.BlockHeight,
|
||||
SaleTxIndex: deploy.TxIndex,
|
||||
FromTier: 0,
|
||||
ToTier: int32(len(deploy.Tiers) - 1),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot get node count from db : %w", err)
|
||||
}
|
||||
|
||||
tiers := make([]protobuf.Tier, len(deploy.Tiers))
|
||||
tierResponses := make([]tierResponse, len(deploy.Tiers))
|
||||
for i, tierJson := range deploy.Tiers {
|
||||
tier := &tiers[i]
|
||||
err := protojson.Unmarshal(tierJson, tier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to decode tiers json : %w", err)
|
||||
}
|
||||
tierResponses[i].Limit = tiers[i].Limit
|
||||
tierResponses[i].MaxPerAddress = tiers[i].MaxPerAddress
|
||||
tierResponses[i].PriceSat = tiers[i].PriceSat
|
||||
tierResponses[i].Sold = nodeCount[i].Count
|
||||
}
|
||||
|
||||
err = ctx.JSON(&deployResponse{
|
||||
Id: deployId,
|
||||
Name: deploy.Name,
|
||||
StartAt: deploy.StartsAt.Time.UTC(),
|
||||
EndAt: deploy.EndsAt.Time.UTC(),
|
||||
Tiers: tierResponses,
|
||||
SellerPublicKey: deploy.SellerPublicKey,
|
||||
MaxPerAddress: deploy.MaxPerAddress,
|
||||
DeployTxHash: deploy.DeployTxHash,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Go fiber cannot parse JSON: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) nodesHandler(ctx *fiber.Ctx) error {
|
||||
deployId := ctx.Query("deployId")
|
||||
if deployId == "" {
|
||||
err := ctx.SendStatus(404)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Go fiber cannot send status: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
ownerPublicKey := ctx.Query("ownerPublicKey")
|
||||
delegateePublicKey := ctx.Query("delegateePublicKey")
|
||||
|
||||
var blockHeight, txIndex int32
|
||||
count, err := fmt.Sscanf(deployId, "%d-%d", &blockHeight, &txIndex)
|
||||
if count != 2 || err != nil {
|
||||
err := ctx.SendStatus(404)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Go fiber cannot send status: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
nodes, err := h.repository.Queries.GetNodesByPubkey(ctx.UserContext(), gen.GetNodesByPubkeyParams{
|
||||
SaleBlock: blockHeight,
|
||||
SaleTxIndex: txIndex,
|
||||
OwnerPublicKey: ownerPublicKey,
|
||||
DelegatedTo: delegateePublicKey,
|
||||
})
|
||||
if err != nil {
|
||||
err := ctx.SendStatus(404)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't get nodes from db: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
responses := make([]nodeResponse, len(nodes))
|
||||
for i, node := range nodes {
|
||||
responses[i].DeployId = deployId
|
||||
responses[i].NodeId = node.NodeID
|
||||
responses[i].TierIndex = node.TierIndex
|
||||
responses[i].DelegatedTo = node.DelegatedTo
|
||||
responses[i].OwnerPublicKey = node.OwnerPublicKey
|
||||
responses[i].PurchaseTxHash = node.PurchaseTxHash
|
||||
responses[i].DelegateTxHash = node.DelegateTxHash
|
||||
responses[i].PurchaseBlockHeight = node.TxIndex
|
||||
}
|
||||
|
||||
err = ctx.JSON(responses)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Go fiber cannot parse JSON: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) eventsHandler(ctx *fiber.Ctx) error {
|
||||
walletAddress := ctx.Query("walletAddress")
|
||||
|
||||
events, err := h.repository.Queries.GetEventsByWallet(ctx.UserContext(), walletAddress)
|
||||
if err != nil {
|
||||
err := ctx.SendStatus(404)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't get events from db: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
responses := make([]eventResposne, len(events))
|
||||
for i, event := range events {
|
||||
responses[i].TxHash = event.TxHash
|
||||
responses[i].BlockHeight = event.BlockHeight
|
||||
responses[i].TxIndex = event.TxIndex
|
||||
responses[i].WalletAddress = event.WalletAddress
|
||||
responses[i].Action = protobuf.Action_name[event.Action]
|
||||
responses[i].ParsedMessage = event.ParsedMessage
|
||||
responses[i].BlockTimestamp = event.BlockTimestamp.Time.UTC()
|
||||
responses[i].BlockHash = event.BlockHash
|
||||
}
|
||||
|
||||
err = ctx.JSON(responses)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Go fiber cannot parse JSON: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
51
modules/nodesale/api/httphandler/responses.go
Normal file
51
modules/nodesale/api/httphandler/responses.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type infoResponse struct {
|
||||
IndexedBlockHeight int32 `json:"indexedBlockHeight"`
|
||||
IndexedBlockHash string `json:"indexedBlockHash"`
|
||||
}
|
||||
|
||||
type deployResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
StartAt time.Time `json:"startAt"`
|
||||
EndAt time.Time `json:"EndAt"`
|
||||
Tiers []tierResponse `json:"tiers"`
|
||||
SellerPublicKey string `json:"sellerPublicKey"`
|
||||
MaxPerAddress int32 `json:"maxPerAddress"`
|
||||
DeployTxHash string `json:"deployTxHash"`
|
||||
}
|
||||
|
||||
type tierResponse struct {
|
||||
PriceSat uint32 `json:"priceSat"`
|
||||
Limit uint32 `json:"limit"`
|
||||
MaxPerAddress uint32 `json:"maxPerAddress"`
|
||||
Sold int64 `json:"sold"`
|
||||
}
|
||||
|
||||
type nodeResponse struct {
|
||||
DeployId string `json:"deployId"`
|
||||
NodeId int32 `json:"nodeId"`
|
||||
TierIndex int32 `json:"tierIndex"`
|
||||
DelegatedTo string `json:"delegatedTo"`
|
||||
OwnerPublicKey string `json:"ownerPublicKey"`
|
||||
PurchaseTxHash string `json:"purchaseTxHash"`
|
||||
DelegateTxHash string `json:"delegateTxHash"`
|
||||
PurchaseBlockHeight int32 `json:"purchaseBlockHeight"`
|
||||
}
|
||||
|
||||
type eventResposne struct {
|
||||
TxHash string `json:"txHash"`
|
||||
BlockHeight int32 `json:"blockHeight"`
|
||||
TxIndex int32 `json:"txIndex"`
|
||||
WalletAddress string `json:"walletAddress"`
|
||||
Action string `json:"action"`
|
||||
ParsedMessage json.RawMessage `json:"parsedMessage"`
|
||||
BlockTimestamp time.Time `json:"blockTimestamp"`
|
||||
BlockHash string `json:"blockHash"`
|
||||
}
|
||||
16
modules/nodesale/api/httphandler/routes.go
Normal file
16
modules/nodesale/api/httphandler/routes.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func (h *handler) Mount(router fiber.Router) error {
|
||||
r := router.Group("/nodesale/v1")
|
||||
|
||||
r.Get("/info", h.infoHandler)
|
||||
r.Get("/deploy/:deployId", h.deployHandler)
|
||||
r.Get("/nodes", h.nodesHandler)
|
||||
r.Get("/events", h.eventsHandler)
|
||||
|
||||
return nil
|
||||
}
|
||||
7
modules/nodesale/config/config.go
Normal file
7
modules/nodesale/config/config.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package config
|
||||
|
||||
import "github.com/gaze-network/indexer-network/internal/postgres"
|
||||
|
||||
type Config struct {
|
||||
Postgres postgres.Config `mapstructure:"postgres"`
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS nodes;
|
||||
DROP TABLE IF EXISTS node_sales;
|
||||
DROP TABLE IF EXISTS events;
|
||||
DROP TABLE IF EXISTS blocks;
|
||||
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,63 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS blocks (
|
||||
"block_height" INTEGER NOT NULL,
|
||||
"block_hash" TEXT NOT NULL,
|
||||
"module" TEXT NOT NULL,
|
||||
PRIMARY KEY("block_height", "block_hash")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
"tx_hash" TEXT NOT NULL PRIMARY KEY,
|
||||
"block_height" INTEGER NOT NULL,
|
||||
"tx_index" INTEGER NOT NULL,
|
||||
"wallet_address" TEXT NOT NULL,
|
||||
"valid" BOOLEAN NOT NULL,
|
||||
"action" INTEGER NOT NULL,
|
||||
"raw_message" BYTEA NOT NULL,
|
||||
"parsed_message" JSONB NOT NULL,
|
||||
"block_timestamp" TIMESTAMP NOT NULL,
|
||||
"block_hash" TEXT NOT NULL,
|
||||
"metadata" JSONB NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO events("tx_hash", "block_height", "tx_index",
|
||||
"wallet_address", "valid", "action",
|
||||
"raw_message", "parsed_message", "block_timestamp",
|
||||
"block_hash", "metadata")
|
||||
VALUES ('', -1, -1,
|
||||
'', false, -1,
|
||||
'', '{}', NOW(),
|
||||
'', '{}');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS node_sales (
|
||||
"block_height" INTEGER NOT NULL,
|
||||
"tx_index" INTEGER NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"starts_at" TIMESTAMP NOT NULL,
|
||||
"ends_at" TIMESTAMP NOT NULL,
|
||||
"tiers" JSONB[] NOT NULL,
|
||||
"seller_public_key" TEXT NOT NULL,
|
||||
"max_per_address" INTEGER NOT NULL,
|
||||
"deploy_tx_hash" TEXT NOT NULL REFERENCES events(tx_hash) ON DELETE CASCADE,
|
||||
"max_discount_percentage" INTEGER NOT NULL,
|
||||
"seller_wallet" TEXT NOT NULL,
|
||||
PRIMARY KEY ("block_height", "tx_index")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS nodes (
|
||||
"sale_block" INTEGER NOT NULL,
|
||||
"sale_tx_index" INTEGER NOT NULL,
|
||||
"node_id" INTEGER NOT NULL,
|
||||
"tier_index" INTEGER NOT NULL,
|
||||
"delegated_to" TEXT NOT NULL DEFAULT '',
|
||||
"owner_public_key" TEXT NOT NULL,
|
||||
"purchase_tx_hash" TEXT NOT NULL REFERENCES events(tx_hash) ON DELETE CASCADE,
|
||||
"delegate_tx_hash" TEXT NOT NULL DEFAULT '' REFERENCES events(tx_hash) ON DELETE SET DEFAULT,
|
||||
PRIMARY KEY("sale_block", "sale_tx_index", "node_id"),
|
||||
FOREIGN KEY("sale_block", "sale_tx_index") REFERENCES node_sales("block_height", "tx_index")
|
||||
);
|
||||
|
||||
|
||||
|
||||
COMMIT;
|
||||
16
modules/nodesale/database/postgresql/queries/blocks.sql
Normal file
16
modules/nodesale/database/postgresql/queries/blocks.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- name: GetLastProcessedBlock :one
|
||||
SELECT * FROM blocks
|
||||
WHERE "block_height" = (SELECT MAX("block_height") FROM blocks);
|
||||
|
||||
|
||||
-- name: GetBlock :one
|
||||
SELECT * FROM blocks
|
||||
WHERE "block_height" = $1;
|
||||
|
||||
-- name: RemoveBlockFrom :execrows
|
||||
DELETE FROM blocks
|
||||
WHERE "block_height" >= @from_block;
|
||||
|
||||
-- name: AddBlock :exec
|
||||
INSERT INTO blocks("block_height", "block_hash", "module")
|
||||
VALUES ($1, $2, $3);
|
||||
13
modules/nodesale/database/postgresql/queries/events.sql
Normal file
13
modules/nodesale/database/postgresql/queries/events.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- name: RemoveEventsFromBlock :execrows
|
||||
DELETE FROM events
|
||||
WHERE "block_height" >= @from_block;
|
||||
|
||||
-- name: AddEvent :exec
|
||||
INSERT INTO events("tx_hash", "block_height", "tx_index", "wallet_address", "valid", "action",
|
||||
"raw_message", "parsed_message", "block_timestamp", "block_hash", "metadata")
|
||||
VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);
|
||||
|
||||
-- name: GetEventsByWallet :many
|
||||
SELECT *
|
||||
FROM events
|
||||
WHERE wallet_address = $1;
|
||||
51
modules/nodesale/database/postgresql/queries/nodes.sql
Normal file
51
modules/nodesale/database/postgresql/queries/nodes.sql
Normal file
@@ -0,0 +1,51 @@
|
||||
-- name: ClearDelegate :execrows
|
||||
UPDATE nodes
|
||||
SET "delegated_to" = ''
|
||||
WHERE "delegate_tx_hash" = NULL;
|
||||
|
||||
-- name: SetDelegates :execrows
|
||||
UPDATE nodes
|
||||
SET delegated_to = @delegatee
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
node_id = ANY (@node_ids::int[]);
|
||||
|
||||
-- name: GetNodes :many
|
||||
SELECT *
|
||||
FROM nodes
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
node_id = ANY (@node_ids::int[]);
|
||||
|
||||
|
||||
-- name: GetNodesByOwner :many
|
||||
SELECT *
|
||||
FROM nodes
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
owner_public_key = $3
|
||||
ORDER BY tier_index;
|
||||
|
||||
-- name: GetNodesByPubkey :many
|
||||
SELECT *
|
||||
FROM nodes JOIN events ON nodes.purchase_tx_hash = events.tx_hash
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
owner_public_key = $3 AND
|
||||
delegated_to = $4;
|
||||
|
||||
-- name: AddNode :exec
|
||||
INSERT INTO nodes(sale_block, sale_tx_index, node_id, tier_index, delegated_to, owner_public_key, purchase_tx_hash, delegate_tx_hash)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8);
|
||||
|
||||
-- name: GetNodeCountByTierIndex :many
|
||||
SELECT tiers.tier_index as tier_index, count(nodes.tier_index)
|
||||
FROM generate_series(@from_tier::int,@to_tier::int) as tiers(tier_index)
|
||||
LEFT JOIN
|
||||
(select *
|
||||
from nodes
|
||||
where sale_block = $1 and
|
||||
sale_tx_index= $2)
|
||||
as nodes on tiers.tier_index = nodes.tier_index
|
||||
group by tiers.tier_index
|
||||
ORDER BY tiers.tier_index;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- name: AddNodesale :exec
|
||||
INSERT INTO node_sales("block_height", "tx_index", "name", "starts_at", "ends_at", "tiers", "seller_public_key", "max_per_address", "deploy_tx_hash", "max_discount_percentage", "seller_wallet")
|
||||
VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);
|
||||
|
||||
-- name: GetNodesale :many
|
||||
SELECT *
|
||||
FROM node_sales
|
||||
WHERE block_height = $1 AND
|
||||
tx_index = $2;
|
||||
4
modules/nodesale/database/postgresql/queries/test.sql
Normal file
4
modules/nodesale/database/postgresql/queries/test.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- name: ClearEvents :exec
|
||||
DELETE FROM events
|
||||
WHERE tx_hash <> '';
|
||||
;
|
||||
83
modules/nodesale/delegate.go
Normal file
83
modules/nodesale/delegate.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
func (p *Processor) processDelegate(ctx context.Context, qtx gen.Querier, block *types.Block, event nodesaleEvent) error {
|
||||
valid := true
|
||||
delegate := event.eventMessage.Delegate
|
||||
nodeIds := make([]int32, len(delegate.NodeIDs))
|
||||
for i, id := range delegate.NodeIDs {
|
||||
nodeIds[i] = int32(id)
|
||||
}
|
||||
nodes, err := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(delegate.DeployID.Block),
|
||||
SaleTxIndex: int32(delegate.DeployID.TxIndex),
|
||||
NodeIds: nodeIds,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to get nodes : %w", err)
|
||||
}
|
||||
|
||||
if len(nodeIds) != len(nodes) {
|
||||
valid = false
|
||||
}
|
||||
|
||||
if valid {
|
||||
for _, node := range nodes {
|
||||
OwnerPublicKeyBytes, err := hex.DecodeString(node.OwnerPublicKey)
|
||||
if err != nil {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
OwnerPublicKey, err := btcec.ParsePubKey(OwnerPublicKeyBytes)
|
||||
if err != nil {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
if !event.txPubkey.IsEqual(OwnerPublicKey) {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = qtx.AddEvent(ctx, gen.AddEventParams{
|
||||
TxHash: event.transaction.TxHash.String(),
|
||||
TxIndex: int32(event.transaction.Index),
|
||||
Action: int32(event.eventMessage.Action),
|
||||
RawMessage: event.rawData,
|
||||
ParsedMessage: event.eventJson,
|
||||
BlockTimestamp: pgtype.Timestamp{Time: block.Header.Timestamp, Valid: true},
|
||||
BlockHash: event.transaction.BlockHash.String(),
|
||||
BlockHeight: int32(event.transaction.BlockHeight),
|
||||
Valid: valid,
|
||||
// WalletAddress: event.txAddress.EncodeAddress(),
|
||||
WalletAddress: p.pubkeyToPkHashAddress(event.txPubkey).EncodeAddress(),
|
||||
Metadata: []byte("{}"),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to insert event : %w", err)
|
||||
}
|
||||
if valid {
|
||||
_, err = qtx.SetDelegates(ctx, gen.SetDelegatesParams{
|
||||
SaleBlock: int32(delegate.DeployID.Block),
|
||||
SaleTxIndex: int32(delegate.DeployID.TxIndex),
|
||||
Delegatee: delegate.DelegateePublicKey,
|
||||
NodeIds: nodeIds,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to set delegate : %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
131
modules/nodesale/delegate_test.go
Normal file
131
modules/nodesale/delegate_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/protobuf"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestDelegate(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
sellerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
|
||||
sellerWallet := p.pubkeyToPkHashAddress(sellerPrivateKey.PubKey())
|
||||
startAt := time.Now().Add(time.Hour * -1)
|
||||
endAt := time.Now().Add(time.Hour * 1)
|
||||
deployMessage := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: uint32(startAt.UTC().Unix()),
|
||||
EndsAt: uint32(endAt.UTC().Unix()),
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 4,
|
||||
MaxPerAddress: 2,
|
||||
},
|
||||
{
|
||||
PriceSat: 400,
|
||||
Limit: 50,
|
||||
MaxPerAddress: 3,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: sellerPubkeyHex,
|
||||
MaxPerAddress: 100,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
event, block := assembleTestEvent(sellerPrivateKey, "111111", "111111", 0, 0, deployMessage)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
buyerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
buyerPubkeyHex := hex.EncodeToString(buyerPrivateKey.PubKey().SerializeCompressed())
|
||||
|
||||
payload := &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 1,
|
||||
TxIndex: uint32(testTxIndex) - 1,
|
||||
},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
NodeIDs: []uint32{9, 10, 11},
|
||||
TotalAmountSat: 600,
|
||||
}
|
||||
|
||||
payloadBytes, _ := proto.Marshal(payload)
|
||||
payloadHash := chainhash.DoubleHashB(payloadBytes)
|
||||
signature := ecdsa.Sign(sellerPrivateKey, payloadHash[:])
|
||||
signatureHex := hex.EncodeToString(signature.Serialize())
|
||||
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: payload,
|
||||
SellerSignature: signatureHex,
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "1212121212", "1212121212", 0, 0, message)
|
||||
|
||||
addr, _ := btcutil.NewAddressPubKey(sellerPrivateKey.PubKey().SerializeCompressed(), p.network.ChainParams())
|
||||
pkscript, _ := txscript.PayToAddrScript(addr.AddressPubKeyHash())
|
||||
event.transaction.TxOut = []*types.TxOut{
|
||||
{
|
||||
PkScript: pkscript,
|
||||
Value: 600,
|
||||
},
|
||||
}
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
|
||||
delegateePrivateKey, _ := btcec.NewPrivateKey()
|
||||
delegateePubkeyHex := hex.EncodeToString(delegateePrivateKey.PubKey().SerializeCompressed())
|
||||
delegateMessage := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DELEGATE,
|
||||
Delegate: &protobuf.ActionDelegate{
|
||||
DelegateePublicKey: delegateePubkeyHex,
|
||||
NodeIDs: []uint32{9, 10},
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 2,
|
||||
TxIndex: uint32(testTxIndex) - 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "131313131313", "131313131313", 0, 0, delegateMessage)
|
||||
p.processDelegate(ctx, qtx, block, event)
|
||||
|
||||
nodes, _ := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(testBlockHeigh) - 3,
|
||||
SaleTxIndex: int32(testTxIndex) - 3,
|
||||
NodeIds: []int32{9, 10, 11},
|
||||
})
|
||||
require.Len(t, nodes, 3)
|
||||
for _, node := range nodes {
|
||||
if node.NodeID == 9 || node.NodeID == 10 {
|
||||
require.NotEmpty(t, node.DelegatedTo)
|
||||
} else if node.NodeID == 11 {
|
||||
require.Empty(t, node.DelegatedTo)
|
||||
} else {
|
||||
require.Fail(t, "Unhandled")
|
||||
}
|
||||
}
|
||||
}
|
||||
86
modules/nodesale/deploy.go
Normal file
86
modules/nodesale/deploy.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
func (p *Processor) processDeploy(ctx context.Context, qtx gen.Querier, block *types.Block, event nodesaleEvent) error {
|
||||
valid := true
|
||||
deploy := event.eventMessage.Deploy
|
||||
|
||||
sellerPubKeyBytes, err := hex.DecodeString(deploy.SellerPublicKey)
|
||||
if err != nil {
|
||||
valid = false
|
||||
}
|
||||
|
||||
if valid {
|
||||
sellerPubKey, err := btcec.ParsePubKey(sellerPubKeyBytes)
|
||||
if err != nil {
|
||||
valid = false
|
||||
}
|
||||
if valid && !event.txPubkey.IsEqual(sellerPubKey) {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
tiers := make([][]byte, len(deploy.Tiers))
|
||||
for i, tier := range deploy.Tiers {
|
||||
tierJson, err := protojson.Marshal(tier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse tiers to json : %w", err)
|
||||
}
|
||||
tiers[i] = tierJson
|
||||
}
|
||||
|
||||
err = qtx.AddEvent(ctx, gen.AddEventParams{
|
||||
TxHash: event.transaction.TxHash.String(),
|
||||
TxIndex: int32(event.transaction.Index),
|
||||
Action: int32(event.eventMessage.Action),
|
||||
RawMessage: event.rawData,
|
||||
ParsedMessage: event.eventJson,
|
||||
BlockTimestamp: pgtype.Timestamp{Time: block.Header.Timestamp, Valid: true},
|
||||
BlockHash: event.transaction.BlockHash.String(),
|
||||
BlockHeight: int32(event.transaction.BlockHeight),
|
||||
Valid: valid,
|
||||
WalletAddress: p.pubkeyToPkHashAddress(event.txPubkey).EncodeAddress(),
|
||||
Metadata: []byte("{}"),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to insert event : %w", err)
|
||||
}
|
||||
if valid {
|
||||
err = qtx.AddNodesale(ctx, gen.AddNodesaleParams{
|
||||
BlockHeight: int32(event.transaction.BlockHeight),
|
||||
TxIndex: int32(event.transaction.Index),
|
||||
Name: deploy.Name,
|
||||
StartsAt: pgtype.Timestamp{
|
||||
Time: time.Unix(int64(deploy.StartsAt), 0).UTC(),
|
||||
Valid: true,
|
||||
},
|
||||
EndsAt: pgtype.Timestamp{
|
||||
Time: time.Unix(int64(deploy.EndsAt), 0).UTC(),
|
||||
Valid: true,
|
||||
},
|
||||
Tiers: tiers,
|
||||
SellerPublicKey: deploy.SellerPublicKey,
|
||||
MaxPerAddress: int32(deploy.MaxPerAddress),
|
||||
DeployTxHash: event.transaction.TxHash.String(),
|
||||
MaxDiscountPercentage: int32(deploy.MaxDiscountPercentage),
|
||||
SellerWallet: deploy.SellerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to insert nodesale : %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
94
modules/nodesale/deploy_test.go
Normal file
94
modules/nodesale/deploy_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/protobuf"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDeployInvalid(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
prvKey, _ := btcec.NewPrivateKey()
|
||||
sellerWallet := p.pubkeyToPkHashAddress(prvKey.PubKey())
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: 100,
|
||||
EndsAt: 200,
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: "0102030405",
|
||||
MaxPerAddress: 100,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
|
||||
event, block := assembleTestEvent(prvKey, "0101010101", "0101010101", 0, 0, message)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
nodesales, _ := qtx.GetNodesale(ctx, gen.GetNodesaleParams{
|
||||
BlockHeight: int32(testBlockHeigh) - 1,
|
||||
TxIndex: int32(testTxIndex) - 1,
|
||||
})
|
||||
require.Len(t, nodesales, 0)
|
||||
}
|
||||
|
||||
func TestDeployValid(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
privateKey, _ := btcec.NewPrivateKey()
|
||||
pubkeyHex := hex.EncodeToString(privateKey.PubKey().SerializeCompressed())
|
||||
sellerWallet := p.pubkeyToPkHashAddress(privateKey.PubKey())
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: 100,
|
||||
EndsAt: 200,
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: pubkeyHex,
|
||||
MaxPerAddress: 100,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
|
||||
event, block := assembleTestEvent(privateKey, "0202020202", "0202020202", 0, 0, message)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
nodesales, _ := qtx.GetNodesale(ctx, gen.GetNodesaleParams{
|
||||
BlockHeight: int32(testBlockHeigh) - 1,
|
||||
TxIndex: int32(testTxIndex) - 1,
|
||||
})
|
||||
require.Len(t, nodesales, 1)
|
||||
}
|
||||
61
modules/nodesale/nodesale.go
Normal file
61
modules/nodesale/nodesale.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/gaze-network/indexer-network/core/datasources"
|
||||
"github.com/gaze-network/indexer-network/core/indexer"
|
||||
"github.com/gaze-network/indexer-network/internal/config"
|
||||
"github.com/gaze-network/indexer-network/internal/postgres"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/api/httphandler"
|
||||
repository "github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/do/v2"
|
||||
)
|
||||
|
||||
var NODESALE_MAGIC = []byte{0x63, 0x73, 0x6f, 0x70}
|
||||
|
||||
const (
|
||||
NODESALE_LASTBLOCK_DEFAULT = 846851
|
||||
Version = "v0.0.1-alpha"
|
||||
)
|
||||
|
||||
func New(injector do.Injector) (indexer.IndexerWorker, error) {
|
||||
ctx := do.MustInvoke[context.Context](injector)
|
||||
conf := do.MustInvoke[config.Config](injector)
|
||||
|
||||
btcClient := do.MustInvoke[*rpcclient.Client](injector)
|
||||
datasource := datasources.NewBitcoinNode(btcClient)
|
||||
|
||||
pg, err := postgres.NewPool(ctx, conf.Modules.Nodesale.Postgres)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can't create postgres connection : %w", err)
|
||||
}
|
||||
var cleanupFuncs []func(context.Context) error
|
||||
cleanupFuncs = append(cleanupFuncs, func(ctx context.Context) error {
|
||||
pg.Close()
|
||||
return nil
|
||||
})
|
||||
repository := repository.NewRepository(pg)
|
||||
|
||||
processor := &Processor{
|
||||
repository: repository,
|
||||
btcClient: datasource,
|
||||
network: conf.Network,
|
||||
cleanupFuncs: cleanupFuncs,
|
||||
}
|
||||
|
||||
httpServer := do.MustInvoke[*fiber.App](injector)
|
||||
nodesaleHandler := httphandler.New(repository)
|
||||
if err := nodesaleHandler.Mount(httpServer); err != nil {
|
||||
return nil, fmt.Errorf("Can't mount nodesale API : %w", err)
|
||||
}
|
||||
logger.InfoContext(ctx, "Mounted nodesale HTTP handler")
|
||||
|
||||
indexer := indexer.New(processor, datasource)
|
||||
logger.InfoContext(ctx, "Nodesale module started.")
|
||||
return indexer, nil
|
||||
}
|
||||
110
modules/nodesale/nodesale_test.go
Normal file
110
modules/nodesale/nodesale_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/internal/postgres"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/protobuf"
|
||||
repository "github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var p *Processor
|
||||
|
||||
var postgresConf postgres.Config = postgres.Config{
|
||||
User: "postgres",
|
||||
Password: "P@ssw0rd",
|
||||
}
|
||||
|
||||
var qtx gen.Querier
|
||||
|
||||
var ctx context.Context
|
||||
|
||||
var tx pgx.Tx
|
||||
|
||||
var (
|
||||
testBlockHeigh int = 101
|
||||
testTxIndex int = 1
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = context.Background()
|
||||
|
||||
db, _ := postgres.NewPool(ctx, postgresConf)
|
||||
|
||||
repo := repository.NewRepository(db)
|
||||
|
||||
p = &Processor{
|
||||
repository: repo,
|
||||
network: common.NetworkMainnet,
|
||||
}
|
||||
repo.Queries.ClearEvents(ctx)
|
||||
|
||||
tx, _ = p.repository.Db.Begin(ctx)
|
||||
qtx = p.repository.WithTx(tx)
|
||||
|
||||
res := m.Run()
|
||||
tx.Commit(ctx)
|
||||
db.Close()
|
||||
os.Exit(res)
|
||||
}
|
||||
|
||||
func assembleTestEvent(privateKey *secp256k1.PrivateKey, blockHashHex, txHashHex string, blockHeight, txIndex int, message *protobuf.NodeSaleEvent) (nodesaleEvent, *types.Block) {
|
||||
blockHash, _ := chainhash.NewHashFromStr(blockHashHex)
|
||||
txHash, _ := chainhash.NewHashFromStr(txHashHex)
|
||||
|
||||
rawData, _ := proto.Marshal(message)
|
||||
|
||||
builder := txscript.NewScriptBuilder()
|
||||
builder.AddOp(txscript.OP_FALSE)
|
||||
builder.AddOp(txscript.OP_IF)
|
||||
builder.AddData(rawData)
|
||||
builder.AddOp(txscript.OP_ENDIF)
|
||||
|
||||
messageJson, _ := protojson.Marshal(message)
|
||||
|
||||
if blockHeight == 0 {
|
||||
blockHeight = testBlockHeigh
|
||||
testBlockHeigh++
|
||||
}
|
||||
if txIndex == 0 {
|
||||
txIndex = testTxIndex
|
||||
testTxIndex++
|
||||
}
|
||||
|
||||
event := nodesaleEvent{
|
||||
transaction: &types.Transaction{
|
||||
BlockHeight: int64(blockHeight),
|
||||
BlockHash: *blockHash,
|
||||
Index: uint32(txIndex),
|
||||
TxHash: *txHash,
|
||||
},
|
||||
rawData: rawData,
|
||||
eventMessage: message,
|
||||
eventJson: messageJson,
|
||||
txPubkey: privateKey.PubKey(),
|
||||
}
|
||||
block := &types.Block{
|
||||
Header: types.BlockHeader{
|
||||
Timestamp: time.Now().UTC(),
|
||||
},
|
||||
}
|
||||
return event, block
|
||||
}
|
||||
290
modules/nodesale/processor.go
Normal file
290
modules/nodesale/processor.go
Normal file
@@ -0,0 +1,290 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/core/indexer"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/gaze-network/indexer-network/core/datasources"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/protobuf"
|
||||
repository "github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
)
|
||||
|
||||
type Processor struct {
|
||||
repository *repository.Repository
|
||||
btcClient *datasources.BitcoinNodeDatasource
|
||||
network common.Network
|
||||
cleanupFuncs []func(context.Context) error
|
||||
}
|
||||
|
||||
// CurrentBlock implements indexer.Processor.
|
||||
func (p *Processor) CurrentBlock(ctx context.Context) (types.BlockHeader, error) {
|
||||
block, err := p.repository.Queries.GetLastProcessedBlock(ctx)
|
||||
if err != nil {
|
||||
logger.InfoContext(ctx, "Couldn't get last processed block. Start from NODESALE_LAST_BLOCK_DEFAULT.",
|
||||
slog.Int("currentBlock", NODESALE_LASTBLOCK_DEFAULT))
|
||||
header, err := p.btcClient.GetBlockHeader(ctx, NODESALE_LASTBLOCK_DEFAULT)
|
||||
if err != nil {
|
||||
return types.BlockHeader{}, fmt.Errorf("Cannot get default block from bitcoin node : %w", err)
|
||||
}
|
||||
return types.BlockHeader{
|
||||
Hash: header.Hash,
|
||||
Height: NODESALE_LASTBLOCK_DEFAULT,
|
||||
}, nil
|
||||
}
|
||||
|
||||
hash, err := chainhash.NewHashFromStr(block.BlockHash)
|
||||
if err != nil {
|
||||
logger.PanicContext(ctx, "Invalid hash format found in Database.")
|
||||
}
|
||||
return types.BlockHeader{
|
||||
Hash: *hash,
|
||||
Height: int64(block.BlockHeight),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetIndexedBlock implements indexer.Processor.
|
||||
func (p *Processor) GetIndexedBlock(ctx context.Context, height int64) (types.BlockHeader, error) {
|
||||
block, err := p.repository.Queries.GetBlock(ctx, int32(height))
|
||||
if err != nil {
|
||||
return types.BlockHeader{}, fmt.Errorf("Block %d not found : %w", height, err)
|
||||
}
|
||||
hash, err := chainhash.NewHashFromStr(block.BlockHash)
|
||||
if err != nil {
|
||||
logger.PanicContext(ctx, "Invalid hash format found in Database.")
|
||||
}
|
||||
return types.BlockHeader{
|
||||
Hash: *hash,
|
||||
Height: int64(block.BlockHeight),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Name implements indexer.Processor.
|
||||
func (p *Processor) Name() string {
|
||||
return "nodesale"
|
||||
}
|
||||
|
||||
func extractNodesaleData(witness [][]byte) (data []byte, internalPubkey *btcec.PublicKey, isNodesale bool) {
|
||||
tokenizer, controlBlock, isTapScript := extractTapScript(witness)
|
||||
if !isTapScript {
|
||||
return []byte{}, nil, false
|
||||
}
|
||||
state := 0
|
||||
for tokenizer.Next() {
|
||||
switch state {
|
||||
case 0:
|
||||
if tokenizer.Opcode() == txscript.OP_0 {
|
||||
state++
|
||||
} else {
|
||||
state = 0
|
||||
}
|
||||
case 1:
|
||||
if tokenizer.Opcode() == txscript.OP_IF {
|
||||
state++
|
||||
} else {
|
||||
state = 0
|
||||
}
|
||||
case 2:
|
||||
if tokenizer.Opcode() == txscript.OP_DATA_4 &&
|
||||
bytes.Equal(tokenizer.Data(), NODESALE_MAGIC) {
|
||||
state++
|
||||
} else {
|
||||
state = 0
|
||||
}
|
||||
case 3:
|
||||
if tokenizer.Opcode() == txscript.OP_PUSHDATA1 {
|
||||
data := tokenizer.Data()
|
||||
return data, controlBlock.InternalKey, true
|
||||
}
|
||||
state = 0
|
||||
}
|
||||
}
|
||||
return []byte{}, nil, false
|
||||
}
|
||||
|
||||
type nodesaleEvent struct {
|
||||
transaction *types.Transaction
|
||||
eventMessage *protobuf.NodeSaleEvent
|
||||
eventJson []byte
|
||||
// txAddress btcutil.Address
|
||||
txPubkey *btcec.PublicKey
|
||||
rawData []byte
|
||||
// rawScript []byte
|
||||
}
|
||||
|
||||
func (p *Processor) parseTransactions(ctx context.Context, transactions []*types.Transaction) ([]nodesaleEvent, error) {
|
||||
var events []nodesaleEvent
|
||||
for _, t := range transactions {
|
||||
for _, txIn := range t.TxIn {
|
||||
data, txPubkey, isNodesale := extractNodesaleData(txIn.Witness)
|
||||
if !isNodesale {
|
||||
continue
|
||||
}
|
||||
|
||||
event := &protobuf.NodeSaleEvent{}
|
||||
err := proto.Unmarshal(data, event)
|
||||
if err != nil {
|
||||
logger.WarnContext(ctx, "Invalid Protobuf",
|
||||
slog.String("block_hash", t.BlockHash.String()),
|
||||
slog.Int("txIndex", int(t.Index)))
|
||||
continue
|
||||
}
|
||||
eventJson, err := protojson.Marshal(event)
|
||||
if err != nil {
|
||||
return []nodesaleEvent{}, fmt.Errorf("Failed to parse protobuf to json : %w", err)
|
||||
}
|
||||
|
||||
/*
|
||||
outIndex := txIn.PreviousOutIndex
|
||||
outHash := txIn.PreviousOutTxHash
|
||||
result, err := p.btcClient.GetTransactionByHash(ctx, outHash)
|
||||
if err != nil {
|
||||
return []nodesaleEvent{}, fmt.Errorf("Failed to Get Bitcoin transaction : %w", err)
|
||||
}
|
||||
pkScript := result.TxOut[outIndex].PkScript
|
||||
_, addresses, _, err := txscript.ExtractPkScriptAddrs(pkScript, p.network.ChainParams())
|
||||
if err != nil {
|
||||
return []nodesaleEvent{}, fmt.Errorf("Failed to Get Bitcoin address : %w", err)
|
||||
}
|
||||
if len(addresses) != 1 {
|
||||
return []nodesaleEvent{}, fmt.Errorf("Multiple addresses detected.")
|
||||
}*/
|
||||
|
||||
events = append(events, nodesaleEvent{
|
||||
transaction: t,
|
||||
eventMessage: event,
|
||||
eventJson: eventJson,
|
||||
// txAddress: addresses[0],
|
||||
rawData: data,
|
||||
txPubkey: txPubkey,
|
||||
// rawScript: rawScript,
|
||||
})
|
||||
}
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Process implements indexer.Processor.
|
||||
func (p *Processor) Process(ctx context.Context, inputs []*types.Block) error {
|
||||
for _, block := range inputs {
|
||||
logger.InfoContext(ctx, "Nodesale processing a block",
|
||||
slog.Int64("block", block.Header.Height),
|
||||
slog.String("hash", block.Header.Hash.String()))
|
||||
// parse all event from each transaction including reading tx wallet
|
||||
events, err := p.parseTransactions(ctx, block.Transactions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid data from bitcoin client : %w", err)
|
||||
}
|
||||
// open transaction
|
||||
tx, err := p.repository.Db.Begin(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create transaction : %w", err)
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
qtx := p.repository.WithTx(tx)
|
||||
|
||||
// write block
|
||||
err = qtx.AddBlock(ctx, gen.AddBlockParams{
|
||||
BlockHeight: int32(block.Header.Height),
|
||||
BlockHash: block.Header.Hash.String(),
|
||||
Module: p.Name(),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to add block %d : %w", block.Header.Height, err)
|
||||
}
|
||||
// for each events
|
||||
for _, event := range events {
|
||||
logger.InfoContext(ctx, "Nodesale processing event",
|
||||
slog.Int("txIndex", int(event.transaction.Index)),
|
||||
slog.Int("blockHeight", int(block.Header.Height)),
|
||||
slog.String("blockhash", block.Header.Hash.String()),
|
||||
)
|
||||
eventMessage := event.eventMessage
|
||||
switch eventMessage.Action {
|
||||
case protobuf.Action_ACTION_DEPLOY:
|
||||
err = p.processDeploy(ctx, qtx, block, event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to deploy at block %d : %w", block.Header.Height, err)
|
||||
}
|
||||
case protobuf.Action_ACTION_DELEGATE:
|
||||
err = p.processDelegate(ctx, qtx, block, event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to delegate at block %d : %w", block.Header.Height, err)
|
||||
}
|
||||
case protobuf.Action_ACTION_PURCHASE:
|
||||
err = p.processPurchase(ctx, qtx, block, event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to purchase at block %d : %w", block.Header.Height, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// close transaction
|
||||
err = tx.Commit(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to commit transaction : %w", err)
|
||||
}
|
||||
logger.InfoContext(ctx, "Nodesale finished processing block",
|
||||
slog.Int64("block", block.Header.Height),
|
||||
slog.String("hash", block.Header.Hash.String()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RevertData implements indexer.Processor.
|
||||
func (p *Processor) RevertData(ctx context.Context, from int64) error {
|
||||
tx, err := p.repository.Db.Begin(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create transaction : %w", err)
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
qtx := p.repository.WithTx(tx)
|
||||
_, err = qtx.RemoveBlockFrom(ctx, int32(from))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to remove blocks. : %w", err)
|
||||
}
|
||||
|
||||
affected, err := qtx.RemoveEventsFromBlock(ctx, int32(from))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to remove events. : %w", err)
|
||||
}
|
||||
_, err = qtx.ClearDelegate(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to clear delegate from nodes : %w", err)
|
||||
}
|
||||
err = tx.Commit(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to commit transaction : %w", err)
|
||||
}
|
||||
logger.InfoContext(ctx, "Events removed",
|
||||
slog.Int("Total removed", int(affected)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyStates implements indexer.Processor.
|
||||
func (p *Processor) VerifyStates(ctx context.Context) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (p *Processor) Shutdown(ctx context.Context) error {
|
||||
for _, cleanupFunc := range p.cleanupFuncs {
|
||||
err := cleanupFunc(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cleanup function error : %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ indexer.Processor[*types.Block] = (*Processor)(nil)
|
||||
7
modules/nodesale/processor_test.go
Normal file
7
modules/nodesale/processor_test.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package nodesale
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRevertData(t *testing.T) {
|
||||
p.RevertData(ctx, 846855)
|
||||
}
|
||||
806
modules/nodesale/protobuf/nodesale.pb.go
Normal file
806
modules/nodesale/protobuf/nodesale.pb.go
Normal file
@@ -0,0 +1,806 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.1
|
||||
// protoc v5.26.1
|
||||
// source: modules/nodesale/protobuf/nodesale.proto
|
||||
|
||||
// protoc modules/nodesale/protobuf/nodesale.proto --go_out=. --go_opt=module=github.com/gaze-network/indexer-network
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Action int32
|
||||
|
||||
const (
|
||||
Action_ACTION_DEPLOY Action = 0
|
||||
Action_ACTION_PURCHASE Action = 1
|
||||
Action_ACTION_DELEGATE Action = 2
|
||||
)
|
||||
|
||||
// Enum value maps for Action.
|
||||
var (
|
||||
Action_name = map[int32]string{
|
||||
0: "ACTION_DEPLOY",
|
||||
1: "ACTION_PURCHASE",
|
||||
2: "ACTION_DELEGATE",
|
||||
}
|
||||
Action_value = map[string]int32{
|
||||
"ACTION_DEPLOY": 0,
|
||||
"ACTION_PURCHASE": 1,
|
||||
"ACTION_DELEGATE": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Action) Enum() *Action {
|
||||
p := new(Action)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Action) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Action) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Action) Type() protoreflect.EnumType {
|
||||
return &file_modules_nodesale_protobuf_nodesale_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Action) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Action.Descriptor instead.
|
||||
func (Action) EnumDescriptor() ([]byte, []int) {
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type NodeSaleEvent struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Action Action `protobuf:"varint,1,opt,name=action,proto3,enum=nodesale.Action" json:"action,omitempty"`
|
||||
Deploy *ActionDeploy `protobuf:"bytes,2,opt,name=deploy,proto3,oneof" json:"deploy,omitempty"`
|
||||
Purchase *ActionPurchase `protobuf:"bytes,3,opt,name=purchase,proto3,oneof" json:"purchase,omitempty"`
|
||||
Delegate *ActionDelegate `protobuf:"bytes,4,opt,name=delegate,proto3,oneof" json:"delegate,omitempty"`
|
||||
}
|
||||
|
||||
func (x *NodeSaleEvent) Reset() {
|
||||
*x = NodeSaleEvent{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *NodeSaleEvent) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NodeSaleEvent) ProtoMessage() {}
|
||||
|
||||
func (x *NodeSaleEvent) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NodeSaleEvent.ProtoReflect.Descriptor instead.
|
||||
func (*NodeSaleEvent) Descriptor() ([]byte, []int) {
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *NodeSaleEvent) GetAction() Action {
|
||||
if x != nil {
|
||||
return x.Action
|
||||
}
|
||||
return Action_ACTION_DEPLOY
|
||||
}
|
||||
|
||||
func (x *NodeSaleEvent) GetDeploy() *ActionDeploy {
|
||||
if x != nil {
|
||||
return x.Deploy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NodeSaleEvent) GetPurchase() *ActionPurchase {
|
||||
if x != nil {
|
||||
return x.Purchase
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NodeSaleEvent) GetDelegate() *ActionDelegate {
|
||||
if x != nil {
|
||||
return x.Delegate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ActionDeploy struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
StartsAt uint32 `protobuf:"varint,2,opt,name=startsAt,proto3" json:"startsAt,omitempty"`
|
||||
EndsAt uint32 `protobuf:"varint,3,opt,name=endsAt,proto3" json:"endsAt,omitempty"`
|
||||
Tiers []*Tier `protobuf:"bytes,4,rep,name=tiers,proto3" json:"tiers,omitempty"`
|
||||
SellerPublicKey string `protobuf:"bytes,5,opt,name=sellerPublicKey,proto3" json:"sellerPublicKey,omitempty"`
|
||||
MaxPerAddress uint32 `protobuf:"varint,6,opt,name=maxPerAddress,proto3" json:"maxPerAddress,omitempty"`
|
||||
MaxDiscountPercentage uint32 `protobuf:"varint,7,opt,name=maxDiscountPercentage,proto3" json:"maxDiscountPercentage,omitempty"`
|
||||
SellerWallet string `protobuf:"bytes,8,opt,name=sellerWallet,proto3" json:"sellerWallet,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) Reset() {
|
||||
*x = ActionDeploy{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ActionDeploy) ProtoMessage() {}
|
||||
|
||||
func (x *ActionDeploy) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ActionDeploy.ProtoReflect.Descriptor instead.
|
||||
func (*ActionDeploy) Descriptor() ([]byte, []int) {
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) GetStartsAt() uint32 {
|
||||
if x != nil {
|
||||
return x.StartsAt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) GetEndsAt() uint32 {
|
||||
if x != nil {
|
||||
return x.EndsAt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) GetTiers() []*Tier {
|
||||
if x != nil {
|
||||
return x.Tiers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) GetSellerPublicKey() string {
|
||||
if x != nil {
|
||||
return x.SellerPublicKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) GetMaxPerAddress() uint32 {
|
||||
if x != nil {
|
||||
return x.MaxPerAddress
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) GetMaxDiscountPercentage() uint32 {
|
||||
if x != nil {
|
||||
return x.MaxDiscountPercentage
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ActionDeploy) GetSellerWallet() string {
|
||||
if x != nil {
|
||||
return x.SellerWallet
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Tier struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
PriceSat uint32 `protobuf:"varint,1,opt,name=priceSat,proto3" json:"priceSat,omitempty"`
|
||||
Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
|
||||
MaxPerAddress uint32 `protobuf:"varint,3,opt,name=maxPerAddress,proto3" json:"maxPerAddress,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Tier) Reset() {
|
||||
*x = Tier{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Tier) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Tier) ProtoMessage() {}
|
||||
|
||||
func (x *Tier) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Tier.ProtoReflect.Descriptor instead.
|
||||
func (*Tier) Descriptor() ([]byte, []int) {
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *Tier) GetPriceSat() uint32 {
|
||||
if x != nil {
|
||||
return x.PriceSat
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Tier) GetLimit() uint32 {
|
||||
if x != nil {
|
||||
return x.Limit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Tier) GetMaxPerAddress() uint32 {
|
||||
if x != nil {
|
||||
return x.MaxPerAddress
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ActionPurchase struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Payload *PurchasePayload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||
SellerSignature string `protobuf:"bytes,2,opt,name=sellerSignature,proto3" json:"sellerSignature,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ActionPurchase) Reset() {
|
||||
*x = ActionPurchase{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ActionPurchase) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ActionPurchase) ProtoMessage() {}
|
||||
|
||||
func (x *ActionPurchase) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ActionPurchase.ProtoReflect.Descriptor instead.
|
||||
func (*ActionPurchase) Descriptor() ([]byte, []int) {
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *ActionPurchase) GetPayload() *PurchasePayload {
|
||||
if x != nil {
|
||||
return x.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ActionPurchase) GetSellerSignature() string {
|
||||
if x != nil {
|
||||
return x.SellerSignature
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type PurchasePayload struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
DeployID *ActionID `protobuf:"bytes,1,opt,name=deployID,proto3" json:"deployID,omitempty"`
|
||||
BuyerPublicKey string `protobuf:"bytes,2,opt,name=buyerPublicKey,proto3" json:"buyerPublicKey,omitempty"`
|
||||
NodeIDs []uint32 `protobuf:"varint,3,rep,packed,name=nodeIDs,proto3" json:"nodeIDs,omitempty"`
|
||||
TotalAmountSat int64 `protobuf:"varint,4,opt,name=totalAmountSat,proto3" json:"totalAmountSat,omitempty"`
|
||||
TimeOutBlock uint64 `protobuf:"varint,5,opt,name=timeOutBlock,proto3" json:"timeOutBlock,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PurchasePayload) Reset() {
|
||||
*x = PurchasePayload{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PurchasePayload) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PurchasePayload) ProtoMessage() {}
|
||||
|
||||
func (x *PurchasePayload) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PurchasePayload.ProtoReflect.Descriptor instead.
|
||||
func (*PurchasePayload) Descriptor() ([]byte, []int) {
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *PurchasePayload) GetDeployID() *ActionID {
|
||||
if x != nil {
|
||||
return x.DeployID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *PurchasePayload) GetBuyerPublicKey() string {
|
||||
if x != nil {
|
||||
return x.BuyerPublicKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PurchasePayload) GetNodeIDs() []uint32 {
|
||||
if x != nil {
|
||||
return x.NodeIDs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *PurchasePayload) GetTotalAmountSat() int64 {
|
||||
if x != nil {
|
||||
return x.TotalAmountSat
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *PurchasePayload) GetTimeOutBlock() uint64 {
|
||||
if x != nil {
|
||||
return x.TimeOutBlock
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ActionID struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Block uint64 `protobuf:"varint,1,opt,name=block,proto3" json:"block,omitempty"`
|
||||
TxIndex uint32 `protobuf:"varint,2,opt,name=txIndex,proto3" json:"txIndex,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ActionID) Reset() {
|
||||
*x = ActionID{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ActionID) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ActionID) ProtoMessage() {}
|
||||
|
||||
func (x *ActionID) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ActionID.ProtoReflect.Descriptor instead.
|
||||
func (*ActionID) Descriptor() ([]byte, []int) {
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *ActionID) GetBlock() uint64 {
|
||||
if x != nil {
|
||||
return x.Block
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ActionID) GetTxIndex() uint32 {
|
||||
if x != nil {
|
||||
return x.TxIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ActionDelegate struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
DelegateePublicKey string `protobuf:"bytes,1,opt,name=delegateePublicKey,proto3" json:"delegateePublicKey,omitempty"`
|
||||
NodeIDs []uint32 `protobuf:"varint,2,rep,packed,name=nodeIDs,proto3" json:"nodeIDs,omitempty"`
|
||||
DeployID *ActionID `protobuf:"bytes,3,opt,name=deployID,proto3" json:"deployID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ActionDelegate) Reset() {
|
||||
*x = ActionDelegate{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ActionDelegate) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ActionDelegate) ProtoMessage() {}
|
||||
|
||||
func (x *ActionDelegate) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_modules_nodesale_protobuf_nodesale_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ActionDelegate.ProtoReflect.Descriptor instead.
|
||||
func (*ActionDelegate) Descriptor() ([]byte, []int) {
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *ActionDelegate) GetDelegateePublicKey() string {
|
||||
if x != nil {
|
||||
return x.DelegateePublicKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ActionDelegate) GetNodeIDs() []uint32 {
|
||||
if x != nil {
|
||||
return x.NodeIDs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ActionDelegate) GetDeployID() *ActionID {
|
||||
if x != nil {
|
||||
return x.DeployID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_modules_nodesale_protobuf_nodesale_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_modules_nodesale_protobuf_nodesale_proto_rawDesc = []byte{
|
||||
0x0a, 0x28, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x61,
|
||||
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x6e, 0x6f, 0x64, 0x65,
|
||||
0x73, 0x61, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6e, 0x6f, 0x64, 0x65,
|
||||
0x73, 0x61, 0x6c, 0x65, 0x22, 0x89, 0x02, 0x0a, 0x0d, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6c,
|
||||
0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x61, 0x6c,
|
||||
0x65, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x12, 0x33, 0x0a, 0x06, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x16, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x61, 0x6c, 0x65, 0x2e, 0x41, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x48, 0x00, 0x52, 0x06, 0x64, 0x65, 0x70, 0x6c,
|
||||
0x6f, 0x79, 0x88, 0x01, 0x01, 0x12, 0x39, 0x0a, 0x08, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73,
|
||||
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x61,
|
||||
0x6c, 0x65, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73,
|
||||
0x65, 0x48, 0x01, 0x52, 0x08, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x88, 0x01, 0x01,
|
||||
0x12, 0x39, 0x0a, 0x08, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x61, 0x6c, 0x65, 0x2e, 0x41, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x48, 0x02, 0x52, 0x08,
|
||||
0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f,
|
||||
0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x75, 0x72, 0x63, 0x68,
|
||||
0x61, 0x73, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65,
|
||||
0x22, 0xa6, 0x02, 0x0a, 0x0c, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x70, 0x6c, 0x6f,
|
||||
0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x41,
|
||||
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x41,
|
||||
0x74, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x64, 0x73, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0d, 0x52, 0x06, 0x65, 0x6e, 0x64, 0x73, 0x41, 0x74, 0x12, 0x24, 0x0a, 0x05, 0x74, 0x69, 0x65,
|
||||
0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x73,
|
||||
0x61, 0x6c, 0x65, 0x2e, 0x54, 0x69, 0x65, 0x72, 0x52, 0x05, 0x74, 0x69, 0x65, 0x72, 0x73, 0x12,
|
||||
0x28, 0x0a, 0x0f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
|
||||
0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72,
|
||||
0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78,
|
||||
0x50, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x0d, 0x6d, 0x61, 0x78, 0x50, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
|
||||
0x34, 0x0a, 0x15, 0x6d, 0x61, 0x78, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x65,
|
||||
0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15,
|
||||
0x6d, 0x61, 0x78, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x65, 0x72, 0x63, 0x65,
|
||||
0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x57,
|
||||
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x6c,
|
||||
0x6c, 0x65, 0x72, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x22, 0x5e, 0x0a, 0x04, 0x54, 0x69, 0x65,
|
||||
0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x53, 0x61, 0x74, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, 0x53, 0x61, 0x74, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69,
|
||||
0x6d, 0x69, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x50, 0x65, 0x72, 0x41, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x50,
|
||||
0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x6f, 0x0a, 0x0e, 0x41, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x70,
|
||||
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6e,
|
||||
0x6f, 0x64, 0x65, 0x73, 0x61, 0x6c, 0x65, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65,
|
||||
0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
0x12, 0x28, 0x0a, 0x0f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74,
|
||||
0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x65, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x0f, 0x50,
|
||||
0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2e,
|
||||
0x0a, 0x08, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x12, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x61, 0x6c, 0x65, 0x2e, 0x41, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x49, 0x44, 0x52, 0x08, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x49, 0x44, 0x12, 0x26,
|
||||
0x0a, 0x0e, 0x62, 0x75, 0x79, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x62, 0x75, 0x79, 0x65, 0x72, 0x50, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x44,
|
||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x44, 0x73,
|
||||
0x12, 0x26, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53,
|
||||
0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41,
|
||||
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x4f, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x4f, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x3a, 0x0a, 0x08,
|
||||
0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63,
|
||||
0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x18,
|
||||
0x0a, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x8a, 0x01, 0x0a, 0x0e, 0x41, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x64,
|
||||
0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65,
|
||||
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74,
|
||||
0x65, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6e,
|
||||
0x6f, 0x64, 0x65, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x07, 0x6e, 0x6f,
|
||||
0x64, 0x65, 0x49, 0x44, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x49,
|
||||
0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x61,
|
||||
0x6c, 0x65, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x08, 0x64, 0x65, 0x70,
|
||||
0x6c, 0x6f, 0x79, 0x49, 0x44, 0x2a, 0x45, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
|
||||
0x11, 0x0a, 0x0d, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x50, 0x4c, 0x4f, 0x59,
|
||||
0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x55, 0x52,
|
||||
0x43, 0x48, 0x41, 0x53, 0x45, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x43, 0x54, 0x49, 0x4f,
|
||||
0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x47, 0x41, 0x54, 0x45, 0x10, 0x02, 0x42, 0x43, 0x5a, 0x41,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x7a, 0x65, 0x2d,
|
||||
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x72, 0x2d,
|
||||
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2f,
|
||||
0x6e, 0x6f, 0x64, 0x65, 0x73, 0x61, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_modules_nodesale_protobuf_nodesale_proto_rawDescOnce sync.Once
|
||||
file_modules_nodesale_protobuf_nodesale_proto_rawDescData = file_modules_nodesale_protobuf_nodesale_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP() []byte {
|
||||
file_modules_nodesale_protobuf_nodesale_proto_rawDescOnce.Do(func() {
|
||||
file_modules_nodesale_protobuf_nodesale_proto_rawDescData = protoimpl.X.CompressGZIP(file_modules_nodesale_protobuf_nodesale_proto_rawDescData)
|
||||
})
|
||||
return file_modules_nodesale_protobuf_nodesale_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_modules_nodesale_protobuf_nodesale_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_modules_nodesale_protobuf_nodesale_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_modules_nodesale_protobuf_nodesale_proto_goTypes = []interface{}{
|
||||
(Action)(0), // 0: nodesale.Action
|
||||
(*NodeSaleEvent)(nil), // 1: nodesale.NodeSaleEvent
|
||||
(*ActionDeploy)(nil), // 2: nodesale.ActionDeploy
|
||||
(*Tier)(nil), // 3: nodesale.Tier
|
||||
(*ActionPurchase)(nil), // 4: nodesale.ActionPurchase
|
||||
(*PurchasePayload)(nil), // 5: nodesale.PurchasePayload
|
||||
(*ActionID)(nil), // 6: nodesale.ActionID
|
||||
(*ActionDelegate)(nil), // 7: nodesale.ActionDelegate
|
||||
}
|
||||
var file_modules_nodesale_protobuf_nodesale_proto_depIdxs = []int32{
|
||||
0, // 0: nodesale.NodeSaleEvent.action:type_name -> nodesale.Action
|
||||
2, // 1: nodesale.NodeSaleEvent.deploy:type_name -> nodesale.ActionDeploy
|
||||
4, // 2: nodesale.NodeSaleEvent.purchase:type_name -> nodesale.ActionPurchase
|
||||
7, // 3: nodesale.NodeSaleEvent.delegate:type_name -> nodesale.ActionDelegate
|
||||
3, // 4: nodesale.ActionDeploy.tiers:type_name -> nodesale.Tier
|
||||
5, // 5: nodesale.ActionPurchase.payload:type_name -> nodesale.PurchasePayload
|
||||
6, // 6: nodesale.PurchasePayload.deployID:type_name -> nodesale.ActionID
|
||||
6, // 7: nodesale.ActionDelegate.deployID:type_name -> nodesale.ActionID
|
||||
8, // [8:8] is the sub-list for method output_type
|
||||
8, // [8:8] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_modules_nodesale_protobuf_nodesale_proto_init() }
|
||||
func file_modules_nodesale_protobuf_nodesale_proto_init() {
|
||||
if File_modules_nodesale_protobuf_nodesale_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_modules_nodesale_protobuf_nodesale_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*NodeSaleEvent); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_modules_nodesale_protobuf_nodesale_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ActionDeploy); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_modules_nodesale_protobuf_nodesale_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Tier); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_modules_nodesale_protobuf_nodesale_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ActionPurchase); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_modules_nodesale_protobuf_nodesale_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PurchasePayload); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_modules_nodesale_protobuf_nodesale_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ActionID); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_modules_nodesale_protobuf_nodesale_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ActionDelegate); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_modules_nodesale_protobuf_nodesale_proto_msgTypes[0].OneofWrappers = []interface{}{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_modules_nodesale_protobuf_nodesale_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_modules_nodesale_protobuf_nodesale_proto_goTypes,
|
||||
DependencyIndexes: file_modules_nodesale_protobuf_nodesale_proto_depIdxs,
|
||||
EnumInfos: file_modules_nodesale_protobuf_nodesale_proto_enumTypes,
|
||||
MessageInfos: file_modules_nodesale_protobuf_nodesale_proto_msgTypes,
|
||||
}.Build()
|
||||
File_modules_nodesale_protobuf_nodesale_proto = out.File
|
||||
file_modules_nodesale_protobuf_nodesale_proto_rawDesc = nil
|
||||
file_modules_nodesale_protobuf_nodesale_proto_goTypes = nil
|
||||
file_modules_nodesale_protobuf_nodesale_proto_depIdxs = nil
|
||||
}
|
||||
60
modules/nodesale/protobuf/nodesale.proto
Normal file
60
modules/nodesale/protobuf/nodesale.proto
Normal file
@@ -0,0 +1,60 @@
|
||||
syntax = "proto3";
|
||||
|
||||
// protoc modules/nodesale/protobuf/nodesale.proto --go_out=. --go_opt=module=github.com/gaze-network/indexer-network
|
||||
|
||||
package nodesale;
|
||||
option go_package = "github.com/gaze-network/indexer-network/modules/nodesale/protobuf";
|
||||
|
||||
enum Action {
|
||||
ACTION_DEPLOY = 0;
|
||||
ACTION_PURCHASE = 1;
|
||||
ACTION_DELEGATE = 2;
|
||||
}
|
||||
|
||||
message NodeSaleEvent {
|
||||
Action action = 1;
|
||||
optional ActionDeploy deploy = 2;
|
||||
optional ActionPurchase purchase = 3;
|
||||
optional ActionDelegate delegate = 4;
|
||||
}
|
||||
|
||||
message ActionDeploy {
|
||||
string name = 1;
|
||||
uint32 startsAt = 2;
|
||||
uint32 endsAt = 3;
|
||||
repeated Tier tiers = 4;
|
||||
string sellerPublicKey = 5;
|
||||
uint32 maxPerAddress = 6;
|
||||
uint32 maxDiscountPercentage = 7;
|
||||
string sellerWallet = 8;
|
||||
}
|
||||
|
||||
message Tier {
|
||||
uint32 priceSat = 1;
|
||||
uint32 limit = 2;
|
||||
uint32 maxPerAddress = 3;
|
||||
}
|
||||
|
||||
message ActionPurchase {
|
||||
PurchasePayload payload = 1;
|
||||
string sellerSignature = 2;
|
||||
}
|
||||
|
||||
message PurchasePayload {
|
||||
ActionID deployID = 1;
|
||||
string buyerPublicKey = 2;
|
||||
repeated uint32 nodeIDs = 3;
|
||||
int64 totalAmountSat = 4;
|
||||
uint64 timeOutBlock = 5;
|
||||
}
|
||||
|
||||
message ActionID {
|
||||
uint64 block = 1;
|
||||
uint32 txIndex = 2;
|
||||
}
|
||||
|
||||
message ActionDelegate {
|
||||
string delegateePublicKey = 1;
|
||||
repeated uint32 nodeIDs = 2;
|
||||
ActionID deployID = 3;
|
||||
}
|
||||
39
modules/nodesale/pubkeyaddr.go
Normal file
39
modules/nodesale/pubkeyaddr.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
)
|
||||
|
||||
/*
|
||||
func (p *Processor) pubkeyToTaprootAddress(pubkey string, script []byte) (btcutil.Address, error) {
|
||||
pubKeyBytes, err := hex.DecodeString(pubkey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to decode string : %w", err)
|
||||
}
|
||||
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse pubkey : %w", err)
|
||||
}
|
||||
|
||||
tapleaf := txscript.NewBaseTapLeaf(script)
|
||||
|
||||
scriptTree := txscript.AssembleTaprootScriptTree(tapleaf)
|
||||
rootHash := scriptTree.RootNode.TapHash()
|
||||
|
||||
tapkey := txscript.ComputeTaprootOutputKey(pubKey, rootHash[:])
|
||||
|
||||
sellerAddr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(tapkey), p.network.ChainParams())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid taproot address: %w", err)
|
||||
}
|
||||
return sellerAddr, nil
|
||||
}*/
|
||||
|
||||
func (p *Processor) pubkeyToPkHashAddress(pubKey *btcec.PublicKey) btcutil.Address {
|
||||
// pubKeyBytes, _ := hex.DecodeString(pubkey)
|
||||
// pubKey, _ := btcec.ParsePubKey(pubKeyBytes)
|
||||
addrPubKey, _ := btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), p.network.ChainParams())
|
||||
addrPubKeyHash := addrPubKey.AddressPubKeyHash()
|
||||
return addrPubKeyHash
|
||||
}
|
||||
270
modules/nodesale/purchase.go
Normal file
270
modules/nodesale/purchase.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/protobuf"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type metaData struct {
|
||||
ExpectedTotalAmountDiscounted int64
|
||||
ReportedTotalAmount int64
|
||||
PaidTotalAmount int64
|
||||
}
|
||||
|
||||
func (p *Processor) processPurchase(ctx context.Context, qtx gen.Querier, block *types.Block, event nodesaleEvent) error {
|
||||
valid := true
|
||||
purchase := event.eventMessage.Purchase
|
||||
payload := purchase.Payload
|
||||
|
||||
buyerPubkeyBytes, err := hex.DecodeString(payload.BuyerPublicKey)
|
||||
if err != nil {
|
||||
valid = false
|
||||
}
|
||||
|
||||
if valid {
|
||||
buyerPubkey, err := btcec.ParsePubKey(buyerPubkeyBytes)
|
||||
if err != nil {
|
||||
valid = false
|
||||
}
|
||||
if valid && !event.txPubkey.IsEqual(buyerPubkey) {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
var deploy *gen.NodeSale
|
||||
if valid {
|
||||
// check node existed
|
||||
deploys, err := qtx.GetNodesale(ctx, gen.GetNodesaleParams{
|
||||
BlockHeight: int32(payload.DeployID.Block),
|
||||
TxIndex: int32(payload.DeployID.TxIndex),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Get nodesale : %w", err)
|
||||
}
|
||||
if len(deploys) < 1 {
|
||||
valid = false
|
||||
} else {
|
||||
deploy = &deploys[0]
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
// check timestamp
|
||||
timestamp := block.Header.Timestamp
|
||||
if timestamp.UTC().Before(deploy.StartsAt.Time.UTC()) ||
|
||||
timestamp.UTC().After(deploy.EndsAt.Time.UTC()) {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
if payload.TimeOutBlock < uint64(event.transaction.BlockHeight) {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
// verified signature
|
||||
payloadBytes, _ := proto.Marshal(payload)
|
||||
signatureBytes, _ := hex.DecodeString(purchase.SellerSignature)
|
||||
signature, err := ecdsa.ParseSignature(signatureBytes)
|
||||
if err != nil {
|
||||
valid = false
|
||||
}
|
||||
if valid {
|
||||
hash := chainhash.DoubleHashB(payloadBytes)
|
||||
pubkeyBytes, _ := hex.DecodeString(deploy.SellerPublicKey)
|
||||
pubKey, _ := btcec.ParsePubKey(pubkeyBytes)
|
||||
verified := signature.Verify(hash[:], pubKey)
|
||||
if !verified {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tiers []protobuf.Tier
|
||||
var buyingTiersCount []uint32
|
||||
nodeIdToTier := make(map[uint32]int32, 1)
|
||||
if valid {
|
||||
// valid nodeID tier
|
||||
tiers = make([]protobuf.Tier, len(deploy.Tiers))
|
||||
for i, tierJson := range deploy.Tiers {
|
||||
tier := &tiers[i]
|
||||
err := protojson.Unmarshal(tierJson, tier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to decode tiers json : %w", err)
|
||||
}
|
||||
}
|
||||
slices.Sort(payload.NodeIDs)
|
||||
buyingTiersCount = make([]uint32, len(tiers))
|
||||
var currentTier int32 = -1
|
||||
var tierSum uint32 = 0
|
||||
for _, nodeId := range payload.NodeIDs {
|
||||
for nodeId >= tierSum && currentTier < int32(len(tiers)-1) {
|
||||
currentTier++
|
||||
tierSum += tiers[currentTier].Limit
|
||||
}
|
||||
if nodeId < tierSum {
|
||||
buyingTiersCount[currentTier]++
|
||||
nodeIdToTier[nodeId] = currentTier
|
||||
} else {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
// valid unpurchased node ID
|
||||
nodeIds := make([]int32, len(payload.NodeIDs))
|
||||
for i, id := range payload.NodeIDs {
|
||||
nodeIds[i] = int32(id)
|
||||
}
|
||||
nodes, err := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(payload.DeployID.Block),
|
||||
SaleTxIndex: int32(payload.DeployID.TxIndex),
|
||||
NodeIds: nodeIds,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Get nodes : %w", err)
|
||||
}
|
||||
if len(nodes) > 0 {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
var sellerAddr btcutil.Address
|
||||
if valid {
|
||||
sellerAddr, err = btcutil.DecodeAddress(deploy.SellerWallet, p.network.ChainParams())
|
||||
if err != nil {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
var txPaid int64 = 0
|
||||
meta := metaData{}
|
||||
if valid {
|
||||
// get total amount paid to seller
|
||||
for _, txOut := range event.transaction.TxOut {
|
||||
_, txOutAddrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.PkScript, p.network.ChainParams())
|
||||
if len(txOutAddrs) == 1 && bytes.Equal(
|
||||
[]byte(sellerAddr.EncodeAddress()),
|
||||
[]byte(txOutAddrs[0].EncodeAddress()),
|
||||
) {
|
||||
txPaid += txOut.Value
|
||||
}
|
||||
}
|
||||
meta.PaidTotalAmount = txPaid
|
||||
meta.ReportedTotalAmount = payload.TotalAmountSat
|
||||
// total amount paid is greater than report paid
|
||||
if txPaid < payload.TotalAmountSat {
|
||||
valid = false
|
||||
}
|
||||
// calculate total price
|
||||
var totalPrice int64 = 0
|
||||
for i := 0; i < len(tiers); i++ {
|
||||
totalPrice += int64(buyingTiersCount[i] * tiers[i].PriceSat)
|
||||
}
|
||||
// report paid is greater than max discounted total price
|
||||
maxDiscounted := totalPrice * (100 - int64(deploy.MaxDiscountPercentage))
|
||||
decimal := maxDiscounted % 100
|
||||
maxDiscounted /= 100
|
||||
if decimal%100 >= 50 {
|
||||
maxDiscounted++
|
||||
}
|
||||
meta.ExpectedTotalAmountDiscounted = maxDiscounted
|
||||
if payload.TotalAmountSat < maxDiscounted {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
var buyerOwnedNodes []gen.Node
|
||||
if valid {
|
||||
var err error
|
||||
// check node limit
|
||||
// get all selled by seller and owned by buyer
|
||||
buyerOwnedNodes, err = qtx.GetNodesByOwner(ctx, gen.GetNodesByOwnerParams{
|
||||
SaleBlock: deploy.BlockHeight,
|
||||
SaleTxIndex: deploy.TxIndex,
|
||||
OwnerPublicKey: payload.BuyerPublicKey,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to GetNodesByOwner : %w", err)
|
||||
}
|
||||
if len(buyerOwnedNodes)+len(payload.NodeIDs) > int(deploy.MaxPerAddress) {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
// check limit
|
||||
// count each tiers
|
||||
// check limited for each tier
|
||||
ownedTiersCount := make([]uint32, len(tiers))
|
||||
for _, node := range buyerOwnedNodes {
|
||||
ownedTiersCount[node.TierIndex]++
|
||||
}
|
||||
for i := 0; i < len(tiers); i++ {
|
||||
if ownedTiersCount[i]+buyingTiersCount[i] > tiers[i].MaxPerAddress {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metaDataBytes, _ := json.Marshal(meta)
|
||||
|
||||
err = qtx.AddEvent(ctx, gen.AddEventParams{
|
||||
TxHash: event.transaction.TxHash.String(),
|
||||
TxIndex: int32(event.transaction.Index),
|
||||
Action: int32(event.eventMessage.Action),
|
||||
RawMessage: event.rawData,
|
||||
ParsedMessage: event.eventJson,
|
||||
BlockTimestamp: pgtype.Timestamp{Time: block.Header.Timestamp, Valid: true},
|
||||
BlockHash: event.transaction.BlockHash.String(),
|
||||
BlockHeight: int32(event.transaction.BlockHeight),
|
||||
Valid: valid,
|
||||
// WalletAddress: event.txAddress.EncodeAddress(),
|
||||
WalletAddress: p.pubkeyToPkHashAddress(event.txPubkey).EncodeAddress(),
|
||||
Metadata: metaDataBytes,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to insert event : %w", err)
|
||||
}
|
||||
|
||||
if valid {
|
||||
// add to node
|
||||
for _, nodeId := range payload.NodeIDs {
|
||||
err := qtx.AddNode(ctx, gen.AddNodeParams{
|
||||
SaleBlock: deploy.BlockHeight,
|
||||
SaleTxIndex: deploy.TxIndex,
|
||||
NodeID: int32(nodeId),
|
||||
TierIndex: nodeIdToTier[nodeId],
|
||||
DelegatedTo: "",
|
||||
OwnerPublicKey: payload.BuyerPublicKey,
|
||||
PurchaseTxHash: event.transaction.TxHash.String(),
|
||||
DelegateTxHash: "",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to insert node : %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
686
modules/nodesale/purchase_test.go
Normal file
686
modules/nodesale/purchase_test.go
Normal file
@@ -0,0 +1,686 @@
|
||||
package nodesale
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/protobuf"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestInvalidPurchase(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
buyerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
buyerPubkeyHex := hex.EncodeToString(buyerPrivateKey.PubKey().SerializeCompressed())
|
||||
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: 111,
|
||||
TxIndex: 1,
|
||||
},
|
||||
NodeIDs: []uint32{1, 2},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TotalAmountSat: 500,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
event, block := assembleTestEvent(buyerPrivateKey, "030303030303", "030303030303", 0, 0, message)
|
||||
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
|
||||
nodes, _ := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: 111,
|
||||
SaleTxIndex: 1,
|
||||
NodeIds: []int32{1, 2},
|
||||
})
|
||||
require.Len(t, nodes, 0)
|
||||
}
|
||||
|
||||
func TestInvalidTimestamp(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
sellerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
|
||||
sellerWallet := p.pubkeyToPkHashAddress(sellerPrivateKey.PubKey())
|
||||
startAt := time.Now().Add(time.Hour * -1)
|
||||
endAt := time.Now().Add(time.Hour * 1)
|
||||
deployMessage := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: uint32(startAt.UTC().Unix()),
|
||||
EndsAt: uint32(endAt.UTC().Unix()),
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: sellerPubkeyHex,
|
||||
MaxPerAddress: 100,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
event, block := assembleTestEvent(sellerPrivateKey, "040404040404", "040404040404", 0, 0, deployMessage)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
buyerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
buyerPubkeyHex := hex.EncodeToString(buyerPrivateKey.PubKey().SerializeCompressed())
|
||||
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 1,
|
||||
TxIndex: uint32(testTxIndex) - 1,
|
||||
},
|
||||
NodeIDs: []uint32{1, 2},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TotalAmountSat: 200,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "050505050505", "050505050505", 0, 0, message)
|
||||
block.Header.Timestamp = time.Now().UTC().Add(time.Hour * 2)
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
|
||||
nodes, _ := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(testBlockHeigh) - 2,
|
||||
SaleTxIndex: int32(testTxIndex) - 2,
|
||||
NodeIds: []int32{1, 2},
|
||||
})
|
||||
require.Len(t, nodes, 0)
|
||||
}
|
||||
|
||||
func TestInvalidBuyerKey(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
sellerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
|
||||
sellerWallet := p.pubkeyToPkHashAddress(sellerPrivateKey.PubKey())
|
||||
startAt := time.Now().Add(time.Hour * -1)
|
||||
endAt := time.Now().Add(time.Hour * 1)
|
||||
deployMessage := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: uint32(startAt.UTC().Unix()),
|
||||
EndsAt: uint32(endAt.UTC().Unix()),
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: sellerPubkeyHex,
|
||||
MaxPerAddress: 100,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
event, block := assembleTestEvent(sellerPrivateKey, "060606060606", "060606060606", 0, 0, deployMessage)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
buyerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 1,
|
||||
TxIndex: uint32(testTxIndex) - 1,
|
||||
},
|
||||
NodeIDs: []uint32{1, 2},
|
||||
BuyerPublicKey: sellerPubkeyHex,
|
||||
TotalAmountSat: 200,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "0707070707", "0707070707", 0, 0, message)
|
||||
block.Header.Timestamp = time.Now().UTC().Add(time.Hour * 2)
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
nodes, _ := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(testBlockHeigh) - 2,
|
||||
SaleTxIndex: int32(testTxIndex) - 2,
|
||||
NodeIds: []int32{1, 2},
|
||||
})
|
||||
require.Len(t, nodes, 0)
|
||||
}
|
||||
|
||||
func TestTimeOut(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
sellerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
|
||||
sellerWallet := p.pubkeyToPkHashAddress(sellerPrivateKey.PubKey())
|
||||
startAt := time.Now().Add(time.Hour * -1)
|
||||
endAt := time.Now().Add(time.Hour * 1)
|
||||
deployMessage := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: uint32(startAt.UTC().Unix()),
|
||||
EndsAt: uint32(endAt.UTC().Unix()),
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: sellerPubkeyHex,
|
||||
MaxPerAddress: 100,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
event, block := assembleTestEvent(sellerPrivateKey, "0808080808", "0808080808", 0, 0, deployMessage)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
buyerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
buyerPubkeyHex := hex.EncodeToString(buyerPrivateKey.PubKey().SerializeCompressed())
|
||||
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 1,
|
||||
TxIndex: uint32(testTxIndex) - 1,
|
||||
},
|
||||
NodeIDs: []uint32{1, 2},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TimeOutBlock: uint64(testBlockHeigh) - 5,
|
||||
TotalAmountSat: 200,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "090909090909", "090909090909", 0, 0, message)
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
nodes, _ := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(testBlockHeigh) - 2,
|
||||
SaleTxIndex: int32(testTxIndex) - 2,
|
||||
NodeIds: []int32{1, 2},
|
||||
})
|
||||
require.Len(t, nodes, 0)
|
||||
}
|
||||
|
||||
func TestSignatureInvalid(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
sellerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
|
||||
sellerWallet := p.pubkeyToPkHashAddress(sellerPrivateKey.PubKey())
|
||||
startAt := time.Now().Add(time.Hour * -1)
|
||||
endAt := time.Now().Add(time.Hour * 1)
|
||||
deployMessage := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: uint32(startAt.UTC().Unix()),
|
||||
EndsAt: uint32(endAt.UTC().Unix()),
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: sellerPubkeyHex,
|
||||
MaxPerAddress: 100,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
event, block := assembleTestEvent(sellerPrivateKey, "0A0A0A0A", "0A0A0A0A", 0, 0, deployMessage)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
buyerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
buyerPubkeyHex := hex.EncodeToString(buyerPrivateKey.PubKey().SerializeCompressed())
|
||||
|
||||
payload := &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 1,
|
||||
TxIndex: uint32(testTxIndex) - 1,
|
||||
},
|
||||
NodeIDs: []uint32{1, 2},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
}
|
||||
|
||||
payloadBytes, _ := proto.Marshal(payload)
|
||||
payloadHash := chainhash.DoubleHashB(payloadBytes)
|
||||
signature := ecdsa.Sign(buyerPrivateKey, payloadHash[:])
|
||||
signatureHex := hex.EncodeToString(signature.Serialize())
|
||||
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: payload,
|
||||
SellerSignature: signatureHex,
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "0B0B0B", "0B0B0B", 0, 0, message)
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
nodes, _ := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(testBlockHeigh) - 2,
|
||||
SaleTxIndex: int32(testTxIndex) - 2,
|
||||
NodeIds: []int32{1, 2},
|
||||
})
|
||||
require.Len(t, nodes, 0)
|
||||
}
|
||||
|
||||
func TestValidPurchase(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
sellerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
|
||||
sellerWallet := p.pubkeyToPkHashAddress(sellerPrivateKey.PubKey())
|
||||
startAt := time.Now().Add(time.Hour * -1)
|
||||
endAt := time.Now().Add(time.Hour * 1)
|
||||
deployMessage := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: uint32(startAt.UTC().Unix()),
|
||||
EndsAt: uint32(endAt.UTC().Unix()),
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 4,
|
||||
MaxPerAddress: 2,
|
||||
},
|
||||
{
|
||||
PriceSat: 400,
|
||||
Limit: 3,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: sellerPubkeyHex,
|
||||
MaxPerAddress: 100,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
event, block := assembleTestEvent(sellerPrivateKey, "0C0C0C0C0C", "0C0C0C0C0C", 0, 0, deployMessage)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
buyerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
buyerPubkeyHex := hex.EncodeToString(buyerPrivateKey.PubKey().SerializeCompressed())
|
||||
|
||||
payload := &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 1,
|
||||
TxIndex: uint32(testTxIndex) - 1,
|
||||
},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
NodeIDs: []uint32{0, 5, 6, 9},
|
||||
TotalAmountSat: 500,
|
||||
}
|
||||
|
||||
payloadBytes, _ := proto.Marshal(payload)
|
||||
payloadHash := chainhash.DoubleHashB(payloadBytes)
|
||||
signature := ecdsa.Sign(sellerPrivateKey, payloadHash[:])
|
||||
signatureHex := hex.EncodeToString(signature.Serialize())
|
||||
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: payload,
|
||||
SellerSignature: signatureHex,
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "0D0D0D0D", "0D0D0D0D", 0, 0, message)
|
||||
|
||||
addr, _ := btcutil.NewAddressPubKey(sellerPrivateKey.PubKey().SerializeCompressed(), p.network.ChainParams())
|
||||
pkscript, _ := txscript.PayToAddrScript(addr.AddressPubKeyHash())
|
||||
event.transaction.TxOut = []*types.TxOut{
|
||||
{
|
||||
PkScript: pkscript,
|
||||
Value: 500,
|
||||
},
|
||||
}
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
nodes, _ := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(testBlockHeigh) - 2,
|
||||
SaleTxIndex: int32(testTxIndex) - 2,
|
||||
NodeIds: []int32{0, 5, 6, 9},
|
||||
})
|
||||
require.Len(t, nodes, 4)
|
||||
ids := make([]int32, len(nodes))
|
||||
for i, id := range nodes {
|
||||
ids[i] = id.NodeID
|
||||
}
|
||||
require.Contains(t, ids, int32(0))
|
||||
require.Contains(t, ids, int32(5))
|
||||
require.Contains(t, ids, int32(6))
|
||||
require.Contains(t, ids, int32(9))
|
||||
}
|
||||
|
||||
func TestBuyingLimit(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
sellerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
|
||||
sellerWallet := p.pubkeyToPkHashAddress(sellerPrivateKey.PubKey())
|
||||
startAt := time.Now().Add(time.Hour * -1)
|
||||
endAt := time.Now().Add(time.Hour * 1)
|
||||
deployMessage := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: uint32(startAt.UTC().Unix()),
|
||||
EndsAt: uint32(endAt.UTC().Unix()),
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 4,
|
||||
MaxPerAddress: 2,
|
||||
},
|
||||
{
|
||||
PriceSat: 400,
|
||||
Limit: 50,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: sellerPubkeyHex,
|
||||
MaxPerAddress: 2,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
event, block := assembleTestEvent(sellerPrivateKey, "2121212121", "2121212121", 0, 0, deployMessage)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
buyerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
buyerPubkeyHex := hex.EncodeToString(buyerPrivateKey.PubKey().SerializeCompressed())
|
||||
|
||||
payload := &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 1,
|
||||
TxIndex: uint32(testTxIndex) - 1,
|
||||
},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
NodeIDs: []uint32{9, 10},
|
||||
TotalAmountSat: 600,
|
||||
}
|
||||
|
||||
payloadBytes, _ := proto.Marshal(payload)
|
||||
payloadHash := chainhash.DoubleHashB(payloadBytes)
|
||||
signature := ecdsa.Sign(sellerPrivateKey, payloadHash[:])
|
||||
signatureHex := hex.EncodeToString(signature.Serialize())
|
||||
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: payload,
|
||||
SellerSignature: signatureHex,
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "2020202020", "2020202020", 0, 0, message)
|
||||
|
||||
addr, _ := btcutil.NewAddressPubKey(sellerPrivateKey.PubKey().SerializeCompressed(), p.network.ChainParams())
|
||||
pkscript, _ := txscript.PayToAddrScript(addr.AddressPubKeyHash())
|
||||
event.transaction.TxOut = []*types.TxOut{
|
||||
{
|
||||
PkScript: pkscript,
|
||||
Value: 600,
|
||||
},
|
||||
}
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
|
||||
tx.Commit(ctx)
|
||||
tx, _ = p.repository.Db.Begin(ctx)
|
||||
qtx = p.repository.WithTx(tx)
|
||||
|
||||
payload = &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 2,
|
||||
TxIndex: uint32(testTxIndex) - 2,
|
||||
},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
NodeIDs: []uint32{11},
|
||||
TotalAmountSat: 600,
|
||||
}
|
||||
|
||||
payloadBytes, _ = proto.Marshal(payload)
|
||||
payloadHash = chainhash.DoubleHashB(payloadBytes)
|
||||
signature = ecdsa.Sign(sellerPrivateKey, payloadHash[:])
|
||||
signatureHex = hex.EncodeToString(signature.Serialize())
|
||||
|
||||
message = &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: payload,
|
||||
SellerSignature: signatureHex,
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "22222222", "22222222", 0, 0, message)
|
||||
|
||||
addr, _ = btcutil.NewAddressPubKey(sellerPrivateKey.PubKey().SerializeCompressed(), p.network.ChainParams())
|
||||
pkscript, _ = txscript.PayToAddrScript(addr.AddressPubKeyHash())
|
||||
event.transaction.TxOut = []*types.TxOut{
|
||||
{
|
||||
PkScript: pkscript,
|
||||
Value: 600,
|
||||
},
|
||||
}
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
|
||||
nodes, _ := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(testBlockHeigh) - 3,
|
||||
SaleTxIndex: int32(testTxIndex) - 3,
|
||||
NodeIds: []int32{9, 10, 11},
|
||||
})
|
||||
require.Len(t, nodes, 2)
|
||||
ids := make([]int32, len(nodes))
|
||||
for i, id := range nodes {
|
||||
ids[i] = id.NodeID
|
||||
}
|
||||
require.Contains(t, ids, int32(9))
|
||||
require.Contains(t, ids, int32(10))
|
||||
}
|
||||
|
||||
func TestBuyingTierLimit(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
sellerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
|
||||
sellerWallet := p.pubkeyToPkHashAddress(sellerPrivateKey.PubKey())
|
||||
startAt := time.Now().Add(time.Hour * -1)
|
||||
endAt := time.Now().Add(time.Hour * 1)
|
||||
deployMessage := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_DEPLOY,
|
||||
Deploy: &protobuf.ActionDeploy{
|
||||
Name: t.Name(),
|
||||
StartsAt: uint32(startAt.UTC().Unix()),
|
||||
EndsAt: uint32(endAt.UTC().Unix()),
|
||||
Tiers: []*protobuf.Tier{
|
||||
{
|
||||
PriceSat: 100,
|
||||
Limit: 5,
|
||||
MaxPerAddress: 100,
|
||||
},
|
||||
{
|
||||
PriceSat: 200,
|
||||
Limit: 4,
|
||||
MaxPerAddress: 2,
|
||||
},
|
||||
{
|
||||
PriceSat: 400,
|
||||
Limit: 50,
|
||||
MaxPerAddress: 3,
|
||||
},
|
||||
},
|
||||
SellerPublicKey: sellerPubkeyHex,
|
||||
MaxPerAddress: 100,
|
||||
MaxDiscountPercentage: 50,
|
||||
SellerWallet: sellerWallet.EncodeAddress(),
|
||||
},
|
||||
}
|
||||
event, block := assembleTestEvent(sellerPrivateKey, "0E0E0E0E", "0E0E0E0E", 0, 0, deployMessage)
|
||||
p.processDeploy(ctx, qtx, block, event)
|
||||
|
||||
buyerPrivateKey, _ := btcec.NewPrivateKey()
|
||||
buyerPubkeyHex := hex.EncodeToString(buyerPrivateKey.PubKey().SerializeCompressed())
|
||||
|
||||
payload := &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 1,
|
||||
TxIndex: uint32(testTxIndex) - 1,
|
||||
},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
NodeIDs: []uint32{9, 10, 11},
|
||||
TotalAmountSat: 600,
|
||||
}
|
||||
|
||||
payloadBytes, _ := proto.Marshal(payload)
|
||||
payloadHash := chainhash.DoubleHashB(payloadBytes)
|
||||
signature := ecdsa.Sign(sellerPrivateKey, payloadHash[:])
|
||||
signatureHex := hex.EncodeToString(signature.Serialize())
|
||||
|
||||
message := &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: payload,
|
||||
SellerSignature: signatureHex,
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "0F0F0F0F0F", "0F0F0F0F0F", 0, 0, message)
|
||||
|
||||
addr, _ := btcutil.NewAddressPubKey(sellerPrivateKey.PubKey().SerializeCompressed(), p.network.ChainParams())
|
||||
pkscript, _ := txscript.PayToAddrScript(addr.AddressPubKeyHash())
|
||||
event.transaction.TxOut = []*types.TxOut{
|
||||
{
|
||||
PkScript: pkscript,
|
||||
Value: 600,
|
||||
},
|
||||
}
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
|
||||
tx.Commit(ctx)
|
||||
tx, _ = p.repository.Db.Begin(ctx)
|
||||
qtx = p.repository.WithTx(tx)
|
||||
|
||||
payload = &protobuf.PurchasePayload{
|
||||
DeployID: &protobuf.ActionID{
|
||||
Block: uint64(testBlockHeigh) - 2,
|
||||
TxIndex: uint32(testTxIndex) - 2,
|
||||
},
|
||||
BuyerPublicKey: buyerPubkeyHex,
|
||||
TimeOutBlock: uint64(testBlockHeigh) + 5,
|
||||
NodeIDs: []uint32{12, 13, 14},
|
||||
TotalAmountSat: 600,
|
||||
}
|
||||
|
||||
payloadBytes, _ = proto.Marshal(payload)
|
||||
payloadHash = chainhash.DoubleHashB(payloadBytes)
|
||||
signature = ecdsa.Sign(sellerPrivateKey, payloadHash[:])
|
||||
signatureHex = hex.EncodeToString(signature.Serialize())
|
||||
|
||||
message = &protobuf.NodeSaleEvent{
|
||||
Action: protobuf.Action_ACTION_PURCHASE,
|
||||
Purchase: &protobuf.ActionPurchase{
|
||||
Payload: payload,
|
||||
SellerSignature: signatureHex,
|
||||
},
|
||||
}
|
||||
|
||||
event, block = assembleTestEvent(buyerPrivateKey, "10101010", "10101010", 0, 0, message)
|
||||
|
||||
addr, _ = btcutil.NewAddressPubKey(sellerPrivateKey.PubKey().SerializeCompressed(), p.network.ChainParams())
|
||||
pkscript, _ = txscript.PayToAddrScript(addr.AddressPubKeyHash())
|
||||
event.transaction.TxOut = []*types.TxOut{
|
||||
{
|
||||
PkScript: pkscript,
|
||||
Value: 600,
|
||||
},
|
||||
}
|
||||
p.processPurchase(ctx, qtx, block, event)
|
||||
|
||||
nodes, _ := qtx.GetNodes(ctx, gen.GetNodesParams{
|
||||
SaleBlock: int32(testBlockHeigh) - 3,
|
||||
SaleTxIndex: int32(testTxIndex) - 3,
|
||||
NodeIds: []int32{9, 10, 11, 12, 13, 14},
|
||||
})
|
||||
require.Len(t, nodes, 3)
|
||||
ids := make([]int32, len(nodes))
|
||||
for i, id := range nodes {
|
||||
ids[i] = id.NodeID
|
||||
}
|
||||
require.Contains(t, ids, int32(9))
|
||||
require.Contains(t, ids, int32(10))
|
||||
require.Contains(t, ids, int32(11))
|
||||
}
|
||||
63
modules/nodesale/repository/postgres/gen/blocks.sql.go
Normal file
63
modules/nodesale/repository/postgres/gen/blocks.sql.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// source: blocks.sql
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const addBlock = `-- name: AddBlock :exec
|
||||
INSERT INTO blocks("block_height", "block_hash", "module")
|
||||
VALUES ($1, $2, $3)
|
||||
`
|
||||
|
||||
type AddBlockParams struct {
|
||||
BlockHeight int32
|
||||
BlockHash string
|
||||
Module string
|
||||
}
|
||||
|
||||
func (q *Queries) AddBlock(ctx context.Context, arg AddBlockParams) error {
|
||||
_, err := q.db.Exec(ctx, addBlock, arg.BlockHeight, arg.BlockHash, arg.Module)
|
||||
return err
|
||||
}
|
||||
|
||||
const getBlock = `-- name: GetBlock :one
|
||||
SELECT block_height, block_hash, module FROM blocks
|
||||
WHERE "block_height" = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetBlock(ctx context.Context, blockHeight int32) (Block, error) {
|
||||
row := q.db.QueryRow(ctx, getBlock, blockHeight)
|
||||
var i Block
|
||||
err := row.Scan(&i.BlockHeight, &i.BlockHash, &i.Module)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getLastProcessedBlock = `-- name: GetLastProcessedBlock :one
|
||||
SELECT block_height, block_hash, module FROM blocks
|
||||
WHERE "block_height" = (SELECT MAX("block_height") FROM blocks)
|
||||
`
|
||||
|
||||
func (q *Queries) GetLastProcessedBlock(ctx context.Context) (Block, error) {
|
||||
row := q.db.QueryRow(ctx, getLastProcessedBlock)
|
||||
var i Block
|
||||
err := row.Scan(&i.BlockHeight, &i.BlockHash, &i.Module)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const removeBlockFrom = `-- name: RemoveBlockFrom :execrows
|
||||
DELETE FROM blocks
|
||||
WHERE "block_height" >= $1
|
||||
`
|
||||
|
||||
func (q *Queries) RemoveBlockFrom(ctx context.Context, fromBlock int32) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, removeBlockFrom, fromBlock)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
32
modules/nodesale/repository/postgres/gen/db.go
Normal file
32
modules/nodesale/repository/postgres/gen/db.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
100
modules/nodesale/repository/postgres/gen/events.sql.go
Normal file
100
modules/nodesale/repository/postgres/gen/events.sql.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// source: events.sql
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const addEvent = `-- name: AddEvent :exec
|
||||
INSERT INTO events("tx_hash", "block_height", "tx_index", "wallet_address", "valid", "action",
|
||||
"raw_message", "parsed_message", "block_timestamp", "block_hash", "metadata")
|
||||
VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
`
|
||||
|
||||
type AddEventParams struct {
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
WalletAddress string
|
||||
Valid bool
|
||||
Action int32
|
||||
RawMessage []byte
|
||||
ParsedMessage []byte
|
||||
BlockTimestamp pgtype.Timestamp
|
||||
BlockHash string
|
||||
Metadata []byte
|
||||
}
|
||||
|
||||
func (q *Queries) AddEvent(ctx context.Context, arg AddEventParams) error {
|
||||
_, err := q.db.Exec(ctx, addEvent,
|
||||
arg.TxHash,
|
||||
arg.BlockHeight,
|
||||
arg.TxIndex,
|
||||
arg.WalletAddress,
|
||||
arg.Valid,
|
||||
arg.Action,
|
||||
arg.RawMessage,
|
||||
arg.ParsedMessage,
|
||||
arg.BlockTimestamp,
|
||||
arg.BlockHash,
|
||||
arg.Metadata,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const getEventsByWallet = `-- name: GetEventsByWallet :many
|
||||
SELECT tx_hash, block_height, tx_index, wallet_address, valid, action, raw_message, parsed_message, block_timestamp, block_hash, metadata
|
||||
FROM events
|
||||
WHERE wallet_address = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetEventsByWallet(ctx context.Context, walletAddress string) ([]Event, error) {
|
||||
rows, err := q.db.Query(ctx, getEventsByWallet, walletAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Event
|
||||
for rows.Next() {
|
||||
var i Event
|
||||
if err := rows.Scan(
|
||||
&i.TxHash,
|
||||
&i.BlockHeight,
|
||||
&i.TxIndex,
|
||||
&i.WalletAddress,
|
||||
&i.Valid,
|
||||
&i.Action,
|
||||
&i.RawMessage,
|
||||
&i.ParsedMessage,
|
||||
&i.BlockTimestamp,
|
||||
&i.BlockHash,
|
||||
&i.Metadata,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const removeEventsFromBlock = `-- name: RemoveEventsFromBlock :execrows
|
||||
DELETE FROM events
|
||||
WHERE "block_height" >= $1
|
||||
`
|
||||
|
||||
func (q *Queries) RemoveEventsFromBlock(ctx context.Context, fromBlock int32) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, removeEventsFromBlock, fromBlock)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
54
modules/nodesale/repository/postgres/gen/models.go
Normal file
54
modules/nodesale/repository/postgres/gen/models.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type Block struct {
|
||||
BlockHeight int32
|
||||
BlockHash string
|
||||
Module string
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
WalletAddress string
|
||||
Valid bool
|
||||
Action int32
|
||||
RawMessage []byte
|
||||
ParsedMessage []byte
|
||||
BlockTimestamp pgtype.Timestamp
|
||||
BlockHash string
|
||||
Metadata []byte
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
SaleBlock int32
|
||||
SaleTxIndex int32
|
||||
NodeID int32
|
||||
TierIndex int32
|
||||
DelegatedTo string
|
||||
OwnerPublicKey string
|
||||
PurchaseTxHash string
|
||||
DelegateTxHash string
|
||||
}
|
||||
|
||||
type NodeSale struct {
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Name string
|
||||
StartsAt pgtype.Timestamp
|
||||
EndsAt pgtype.Timestamp
|
||||
Tiers [][]byte
|
||||
SellerPublicKey string
|
||||
MaxPerAddress int32
|
||||
DeployTxHash string
|
||||
MaxDiscountPercentage int32
|
||||
SellerWallet string
|
||||
}
|
||||
304
modules/nodesale/repository/postgres/gen/nodes.sql.go
Normal file
304
modules/nodesale/repository/postgres/gen/nodes.sql.go
Normal file
@@ -0,0 +1,304 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// source: nodes.sql
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const addNode = `-- name: AddNode :exec
|
||||
INSERT INTO nodes(sale_block, sale_tx_index, node_id, tier_index, delegated_to, owner_public_key, purchase_tx_hash, delegate_tx_hash)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
`
|
||||
|
||||
type AddNodeParams struct {
|
||||
SaleBlock int32
|
||||
SaleTxIndex int32
|
||||
NodeID int32
|
||||
TierIndex int32
|
||||
DelegatedTo string
|
||||
OwnerPublicKey string
|
||||
PurchaseTxHash string
|
||||
DelegateTxHash string
|
||||
}
|
||||
|
||||
func (q *Queries) AddNode(ctx context.Context, arg AddNodeParams) error {
|
||||
_, err := q.db.Exec(ctx, addNode,
|
||||
arg.SaleBlock,
|
||||
arg.SaleTxIndex,
|
||||
arg.NodeID,
|
||||
arg.TierIndex,
|
||||
arg.DelegatedTo,
|
||||
arg.OwnerPublicKey,
|
||||
arg.PurchaseTxHash,
|
||||
arg.DelegateTxHash,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const clearDelegate = `-- name: ClearDelegate :execrows
|
||||
UPDATE nodes
|
||||
SET "delegated_to" = ''
|
||||
WHERE "delegate_tx_hash" = NULL
|
||||
`
|
||||
|
||||
func (q *Queries) ClearDelegate(ctx context.Context) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, clearDelegate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const getNodeCountByTierIndex = `-- name: GetNodeCountByTierIndex :many
|
||||
SELECT tiers.tier_index as tier_index, count(nodes.tier_index)
|
||||
FROM generate_series($3::int,$4::int) as tiers(tier_index)
|
||||
LEFT JOIN
|
||||
(select sale_block, sale_tx_index, node_id, tier_index, delegated_to, owner_public_key, purchase_tx_hash, delegate_tx_hash
|
||||
from nodes
|
||||
where sale_block = $1 and
|
||||
sale_tx_index= $2)
|
||||
as nodes on tiers.tier_index = nodes.tier_index
|
||||
group by tiers.tier_index
|
||||
ORDER BY tiers.tier_index
|
||||
`
|
||||
|
||||
type GetNodeCountByTierIndexParams struct {
|
||||
SaleBlock int32
|
||||
SaleTxIndex int32
|
||||
FromTier int32
|
||||
ToTier int32
|
||||
}
|
||||
|
||||
type GetNodeCountByTierIndexRow struct {
|
||||
TierIndex interface{}
|
||||
Count int64
|
||||
}
|
||||
|
||||
func (q *Queries) GetNodeCountByTierIndex(ctx context.Context, arg GetNodeCountByTierIndexParams) ([]GetNodeCountByTierIndexRow, error) {
|
||||
rows, err := q.db.Query(ctx, getNodeCountByTierIndex,
|
||||
arg.SaleBlock,
|
||||
arg.SaleTxIndex,
|
||||
arg.FromTier,
|
||||
arg.ToTier,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetNodeCountByTierIndexRow
|
||||
for rows.Next() {
|
||||
var i GetNodeCountByTierIndexRow
|
||||
if err := rows.Scan(&i.TierIndex, &i.Count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getNodes = `-- name: GetNodes :many
|
||||
SELECT sale_block, sale_tx_index, node_id, tier_index, delegated_to, owner_public_key, purchase_tx_hash, delegate_tx_hash
|
||||
FROM nodes
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
node_id = ANY ($3::int[])
|
||||
`
|
||||
|
||||
type GetNodesParams struct {
|
||||
SaleBlock int32
|
||||
SaleTxIndex int32
|
||||
NodeIds []int32
|
||||
}
|
||||
|
||||
func (q *Queries) GetNodes(ctx context.Context, arg GetNodesParams) ([]Node, error) {
|
||||
rows, err := q.db.Query(ctx, getNodes, arg.SaleBlock, arg.SaleTxIndex, arg.NodeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Node
|
||||
for rows.Next() {
|
||||
var i Node
|
||||
if err := rows.Scan(
|
||||
&i.SaleBlock,
|
||||
&i.SaleTxIndex,
|
||||
&i.NodeID,
|
||||
&i.TierIndex,
|
||||
&i.DelegatedTo,
|
||||
&i.OwnerPublicKey,
|
||||
&i.PurchaseTxHash,
|
||||
&i.DelegateTxHash,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getNodesByOwner = `-- name: GetNodesByOwner :many
|
||||
SELECT sale_block, sale_tx_index, node_id, tier_index, delegated_to, owner_public_key, purchase_tx_hash, delegate_tx_hash
|
||||
FROM nodes
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
owner_public_key = $3
|
||||
ORDER BY tier_index
|
||||
`
|
||||
|
||||
type GetNodesByOwnerParams struct {
|
||||
SaleBlock int32
|
||||
SaleTxIndex int32
|
||||
OwnerPublicKey string
|
||||
}
|
||||
|
||||
func (q *Queries) GetNodesByOwner(ctx context.Context, arg GetNodesByOwnerParams) ([]Node, error) {
|
||||
rows, err := q.db.Query(ctx, getNodesByOwner, arg.SaleBlock, arg.SaleTxIndex, arg.OwnerPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Node
|
||||
for rows.Next() {
|
||||
var i Node
|
||||
if err := rows.Scan(
|
||||
&i.SaleBlock,
|
||||
&i.SaleTxIndex,
|
||||
&i.NodeID,
|
||||
&i.TierIndex,
|
||||
&i.DelegatedTo,
|
||||
&i.OwnerPublicKey,
|
||||
&i.PurchaseTxHash,
|
||||
&i.DelegateTxHash,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getNodesByPubkey = `-- name: GetNodesByPubkey :many
|
||||
SELECT sale_block, sale_tx_index, node_id, tier_index, delegated_to, owner_public_key, purchase_tx_hash, delegate_tx_hash, tx_hash, block_height, tx_index, wallet_address, valid, action, raw_message, parsed_message, block_timestamp, block_hash, metadata
|
||||
FROM nodes JOIN events ON nodes.purchase_tx_hash = events.tx_hash
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
owner_public_key = $3 AND
|
||||
delegated_to = $4
|
||||
`
|
||||
|
||||
type GetNodesByPubkeyParams struct {
|
||||
SaleBlock int32
|
||||
SaleTxIndex int32
|
||||
OwnerPublicKey string
|
||||
DelegatedTo string
|
||||
}
|
||||
|
||||
type GetNodesByPubkeyRow struct {
|
||||
SaleBlock int32
|
||||
SaleTxIndex int32
|
||||
NodeID int32
|
||||
TierIndex int32
|
||||
DelegatedTo string
|
||||
OwnerPublicKey string
|
||||
PurchaseTxHash string
|
||||
DelegateTxHash string
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
WalletAddress string
|
||||
Valid bool
|
||||
Action int32
|
||||
RawMessage []byte
|
||||
ParsedMessage []byte
|
||||
BlockTimestamp pgtype.Timestamp
|
||||
BlockHash string
|
||||
Metadata []byte
|
||||
}
|
||||
|
||||
func (q *Queries) GetNodesByPubkey(ctx context.Context, arg GetNodesByPubkeyParams) ([]GetNodesByPubkeyRow, error) {
|
||||
rows, err := q.db.Query(ctx, getNodesByPubkey,
|
||||
arg.SaleBlock,
|
||||
arg.SaleTxIndex,
|
||||
arg.OwnerPublicKey,
|
||||
arg.DelegatedTo,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetNodesByPubkeyRow
|
||||
for rows.Next() {
|
||||
var i GetNodesByPubkeyRow
|
||||
if err := rows.Scan(
|
||||
&i.SaleBlock,
|
||||
&i.SaleTxIndex,
|
||||
&i.NodeID,
|
||||
&i.TierIndex,
|
||||
&i.DelegatedTo,
|
||||
&i.OwnerPublicKey,
|
||||
&i.PurchaseTxHash,
|
||||
&i.DelegateTxHash,
|
||||
&i.TxHash,
|
||||
&i.BlockHeight,
|
||||
&i.TxIndex,
|
||||
&i.WalletAddress,
|
||||
&i.Valid,
|
||||
&i.Action,
|
||||
&i.RawMessage,
|
||||
&i.ParsedMessage,
|
||||
&i.BlockTimestamp,
|
||||
&i.BlockHash,
|
||||
&i.Metadata,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const setDelegates = `-- name: SetDelegates :execrows
|
||||
UPDATE nodes
|
||||
SET delegated_to = $3
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
node_id = ANY ($4::int[])
|
||||
`
|
||||
|
||||
type SetDelegatesParams struct {
|
||||
SaleBlock int32
|
||||
SaleTxIndex int32
|
||||
Delegatee string
|
||||
NodeIds []int32
|
||||
}
|
||||
|
||||
func (q *Queries) SetDelegates(ctx context.Context, arg SetDelegatesParams) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, setDelegates,
|
||||
arg.SaleBlock,
|
||||
arg.SaleTxIndex,
|
||||
arg.Delegatee,
|
||||
arg.NodeIds,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
92
modules/nodesale/repository/postgres/gen/nodesales.sql.go
Normal file
92
modules/nodesale/repository/postgres/gen/nodesales.sql.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// source: nodesales.sql
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const addNodesale = `-- name: AddNodesale :exec
|
||||
INSERT INTO node_sales("block_height", "tx_index", "name", "starts_at", "ends_at", "tiers", "seller_public_key", "max_per_address", "deploy_tx_hash", "max_discount_percentage", "seller_wallet")
|
||||
VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
`
|
||||
|
||||
type AddNodesaleParams struct {
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Name string
|
||||
StartsAt pgtype.Timestamp
|
||||
EndsAt pgtype.Timestamp
|
||||
Tiers [][]byte
|
||||
SellerPublicKey string
|
||||
MaxPerAddress int32
|
||||
DeployTxHash string
|
||||
MaxDiscountPercentage int32
|
||||
SellerWallet string
|
||||
}
|
||||
|
||||
func (q *Queries) AddNodesale(ctx context.Context, arg AddNodesaleParams) error {
|
||||
_, err := q.db.Exec(ctx, addNodesale,
|
||||
arg.BlockHeight,
|
||||
arg.TxIndex,
|
||||
arg.Name,
|
||||
arg.StartsAt,
|
||||
arg.EndsAt,
|
||||
arg.Tiers,
|
||||
arg.SellerPublicKey,
|
||||
arg.MaxPerAddress,
|
||||
arg.DeployTxHash,
|
||||
arg.MaxDiscountPercentage,
|
||||
arg.SellerWallet,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const getNodesale = `-- name: GetNodesale :many
|
||||
SELECT block_height, tx_index, name, starts_at, ends_at, tiers, seller_public_key, max_per_address, deploy_tx_hash, max_discount_percentage, seller_wallet
|
||||
FROM node_sales
|
||||
WHERE block_height = $1 AND
|
||||
tx_index = $2
|
||||
`
|
||||
|
||||
type GetNodesaleParams struct {
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
}
|
||||
|
||||
func (q *Queries) GetNodesale(ctx context.Context, arg GetNodesaleParams) ([]NodeSale, error) {
|
||||
rows, err := q.db.Query(ctx, getNodesale, arg.BlockHeight, arg.TxIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []NodeSale
|
||||
for rows.Next() {
|
||||
var i NodeSale
|
||||
if err := rows.Scan(
|
||||
&i.BlockHeight,
|
||||
&i.TxIndex,
|
||||
&i.Name,
|
||||
&i.StartsAt,
|
||||
&i.EndsAt,
|
||||
&i.Tiers,
|
||||
&i.SellerPublicKey,
|
||||
&i.MaxPerAddress,
|
||||
&i.DeployTxHash,
|
||||
&i.MaxDiscountPercentage,
|
||||
&i.SellerWallet,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
31
modules/nodesale/repository/postgres/gen/querier.go
Normal file
31
modules/nodesale/repository/postgres/gen/querier.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Querier interface {
|
||||
AddBlock(ctx context.Context, arg AddBlockParams) error
|
||||
AddEvent(ctx context.Context, arg AddEventParams) error
|
||||
AddNode(ctx context.Context, arg AddNodeParams) error
|
||||
AddNodesale(ctx context.Context, arg AddNodesaleParams) error
|
||||
ClearDelegate(ctx context.Context) (int64, error)
|
||||
ClearEvents(ctx context.Context) error
|
||||
GetBlock(ctx context.Context, blockHeight int32) (Block, error)
|
||||
GetEventsByWallet(ctx context.Context, walletAddress string) ([]Event, error)
|
||||
GetLastProcessedBlock(ctx context.Context) (Block, error)
|
||||
GetNodeCountByTierIndex(ctx context.Context, arg GetNodeCountByTierIndexParams) ([]GetNodeCountByTierIndexRow, error)
|
||||
GetNodes(ctx context.Context, arg GetNodesParams) ([]Node, error)
|
||||
GetNodesByOwner(ctx context.Context, arg GetNodesByOwnerParams) ([]Node, error)
|
||||
GetNodesByPubkey(ctx context.Context, arg GetNodesByPubkeyParams) ([]GetNodesByPubkeyRow, error)
|
||||
GetNodesale(ctx context.Context, arg GetNodesaleParams) ([]NodeSale, error)
|
||||
RemoveBlockFrom(ctx context.Context, fromBlock int32) (int64, error)
|
||||
RemoveEventsFromBlock(ctx context.Context, fromBlock int32) (int64, error)
|
||||
SetDelegates(ctx context.Context, arg SetDelegatesParams) (int64, error)
|
||||
}
|
||||
|
||||
var _ Querier = (*Queries)(nil)
|
||||
20
modules/nodesale/repository/postgres/gen/test.sql.go
Normal file
20
modules/nodesale/repository/postgres/gen/test.sql.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// source: test.sql
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const clearEvents = `-- name: ClearEvents :exec
|
||||
DELETE FROM events
|
||||
WHERE tx_hash <> ''
|
||||
`
|
||||
|
||||
func (q *Queries) ClearEvents(ctx context.Context) error {
|
||||
_, err := q.db.Exec(ctx, clearEvents)
|
||||
return err
|
||||
}
|
||||
24
modules/nodesale/repository/postgres/repository.go
Normal file
24
modules/nodesale/repository/postgres/repository.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
db "github.com/gaze-network/indexer-network/internal/postgres"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/repository/postgres/gen"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
Db db.TxQueryable
|
||||
Queries gen.Querier
|
||||
}
|
||||
|
||||
func NewRepository(db db.DB) *Repository {
|
||||
return &Repository{
|
||||
Db: db,
|
||||
Queries: gen.New(db),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Repository) WithTx(tx pgx.Tx) gen.Querier {
|
||||
queries := gen.Queries{}
|
||||
return queries.WithTx(tx)
|
||||
}
|
||||
25
modules/nodesale/tapscript.go
Normal file
25
modules/nodesale/tapscript.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package nodesale
|
||||
|
||||
import "github.com/btcsuite/btcd/txscript"
|
||||
|
||||
func extractTapScript(witness [][]byte) (tokenizer txscript.ScriptTokenizer, controlBlock *txscript.ControlBlock, isTapScript bool) {
|
||||
witness = removeAnnexFromWitness(witness)
|
||||
if len(witness) < 2 {
|
||||
return txscript.ScriptTokenizer{}, nil, false
|
||||
}
|
||||
script := witness[len(witness)-2]
|
||||
rawControl := witness[len(witness)-1]
|
||||
parsedControl, err := txscript.ParseControlBlock(rawControl)
|
||||
if err != nil {
|
||||
return txscript.ScriptTokenizer{}, nil, false
|
||||
}
|
||||
|
||||
return txscript.MakeScriptTokenizer(0, script), parsedControl, true
|
||||
}
|
||||
|
||||
func removeAnnexFromWitness(witness [][]byte) [][]byte {
|
||||
if len(witness) >= 2 && len(witness[len(witness)-1]) > 0 && witness[len(witness)-1][0] == txscript.TaprootAnnexTag {
|
||||
return witness[:len(witness)-1]
|
||||
}
|
||||
return witness
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package httphandler
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
@@ -15,11 +14,9 @@ import (
|
||||
)
|
||||
|
||||
type getTransactionsRequest struct {
|
||||
Wallet string `query:"wallet"`
|
||||
Id string `query:"id"`
|
||||
|
||||
FromBlock int64 `query:"fromBlock"`
|
||||
ToBlock int64 `query:"toBlock"`
|
||||
Wallet string `query:"wallet"`
|
||||
Id string `query:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
}
|
||||
|
||||
func (r getTransactionsRequest) Validate() error {
|
||||
@@ -27,12 +24,6 @@ func (r getTransactionsRequest) Validate() error {
|
||||
if r.Id != "" && !isRuneIdOrRuneName(r.Id) {
|
||||
errList = append(errList, errors.New("'id' is not valid rune id or rune name"))
|
||||
}
|
||||
if r.FromBlock < -1 {
|
||||
errList = append(errList, errors.Errorf("invalid fromBlock range"))
|
||||
}
|
||||
if r.ToBlock < -1 {
|
||||
errList = append(errList, errors.Errorf("invalid toBlock range"))
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
@@ -134,31 +125,17 @@ func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// default to latest block
|
||||
if req.ToBlock == 0 {
|
||||
req.ToBlock = -1
|
||||
}
|
||||
|
||||
// get latest block height if block height is -1
|
||||
if req.FromBlock == -1 || req.ToBlock == -1 {
|
||||
blockHeight := req.BlockHeight
|
||||
// set blockHeight to the latest block height blockHeight, pkScript, and runeId are not provided
|
||||
if blockHeight == 0 && pkScript == nil && runeId == (runes.RuneId{}) {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
if req.FromBlock == -1 {
|
||||
req.FromBlock = blockHeader.Height
|
||||
}
|
||||
if req.ToBlock == -1 {
|
||||
req.ToBlock = blockHeader.Height
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
// validate block height range
|
||||
if req.FromBlock > req.ToBlock {
|
||||
return errs.NewPublicError(fmt.Sprintf("fromBlock must be less than or equal to toBlock, got fromBlock=%d, toBlock=%d", req.FromBlock, req.ToBlock))
|
||||
}
|
||||
|
||||
txs, err := h.usecase.GetRuneTransactions(ctx.UserContext(), pkScript, runeId, uint64(req.FromBlock), uint64(req.ToBlock))
|
||||
txs, err := h.usecase.GetRuneTransactions(ctx.UserContext(), pkScript, runeId, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetRuneTransactions")
|
||||
}
|
||||
|
||||
@@ -55,9 +55,8 @@ SELECT * FROM runes_transactions
|
||||
OR runes_transactions.burns ? @rune_id
|
||||
OR (runes_transactions.rune_etched = TRUE AND runes_transactions.block_height = @rune_id_block_height AND runes_transactions.index = @rune_id_tx_index)
|
||||
) AND (
|
||||
@from_block <= runes_transactions.block_height AND runes_transactions.block_height <= @to_block
|
||||
)
|
||||
ORDER BY runes_transactions.block_height DESC LIMIT 10000;
|
||||
@block_height::INT = 0 OR runes_transactions.block_height = @block_height::INT -- if @block_height > 0, apply block_height filter
|
||||
);
|
||||
|
||||
-- name: CountRuneEntries :one
|
||||
SELECT COUNT(*) FROM runes_entries;
|
||||
|
||||
@@ -27,7 +27,7 @@ type RunesReaderDataGateway interface {
|
||||
GetLatestBlock(ctx context.Context) (types.BlockHeader, error)
|
||||
GetIndexedBlockByHeight(ctx context.Context, height int64) (*entity.IndexedBlock, error)
|
||||
// GetRuneTransactions returns the runes transactions, filterable by pkScript, runeId and height. If pkScript, runeId or height is zero value, that filter is ignored.
|
||||
GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, fromBlock, toBlock uint64) ([]*entity.RuneTransaction, error)
|
||||
GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, height uint64) ([]*entity.RuneTransaction, error)
|
||||
|
||||
GetRunesBalancesAtOutPoint(ctx context.Context, outPoint wire.OutPoint) (map[runes.RuneId]*entity.OutPointBalance, error)
|
||||
GetUnspentOutPointBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.OutPointBalance, error)
|
||||
|
||||
@@ -146,7 +146,7 @@ func (p *Processor) processTx(ctx context.Context, tx *types.Transaction, blockH
|
||||
// find all non-OP_RETURN outputs
|
||||
var destinations []int
|
||||
for i, txOut := range tx.TxOut {
|
||||
if !txOut.IsOpReturn() {
|
||||
if txOut.IsOpReturn() {
|
||||
destinations = append(destinations, i)
|
||||
}
|
||||
}
|
||||
@@ -466,7 +466,7 @@ func (p *Processor) txCommitsToRune(ctx context.Context, tx *types.Transaction,
|
||||
// It is impossible to verify that input utxo is a P2TR output with just the input.
|
||||
// Need to verify with utxo's pk script.
|
||||
|
||||
prevTx, blockHeight, err := p.bitcoinClient.GetRawTransactionAndHeightByTxHash(ctx, txIn.PreviousOutTxHash)
|
||||
prevTx, err := p.bitcoinClient.GetTransactionByHash(ctx, txIn.PreviousOutTxHash)
|
||||
if err != nil && errors.Is(err, errs.NotFound) {
|
||||
continue
|
||||
}
|
||||
@@ -479,7 +479,7 @@ func (p *Processor) txCommitsToRune(ctx context.Context, tx *types.Transaction,
|
||||
break
|
||||
}
|
||||
// input must be mature enough
|
||||
confirmations := tx.BlockHeight - blockHeight + 1
|
||||
confirmations := tx.BlockHeight - prevTx.BlockHeight + 1
|
||||
if confirmations < runes.RUNE_COMMIT_BLOCKS {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -646,9 +646,8 @@ SELECT hash, runes_transactions.block_height, index, timestamp, inputs, outputs,
|
||||
OR runes_transactions.burns ? $5
|
||||
OR (runes_transactions.rune_etched = TRUE AND runes_transactions.block_height = $6 AND runes_transactions.index = $7)
|
||||
) AND (
|
||||
$8 <= runes_transactions.block_height AND runes_transactions.block_height <= $9
|
||||
$8::INT = 0 OR runes_transactions.block_height = $8::INT -- if @block_height > 0, apply block_height filter
|
||||
)
|
||||
ORDER BY runes_transactions.block_height DESC LIMIT 10000
|
||||
`
|
||||
|
||||
type GetRuneTransactionsParams struct {
|
||||
@@ -659,8 +658,7 @@ type GetRuneTransactionsParams struct {
|
||||
RuneID []byte
|
||||
RuneIDBlockHeight int32
|
||||
RuneIDTxIndex int32
|
||||
FromBlock int32
|
||||
ToBlock int32
|
||||
BlockHeight int32
|
||||
}
|
||||
|
||||
type GetRuneTransactionsRow struct {
|
||||
@@ -705,8 +703,7 @@ func (q *Queries) GetRuneTransactions(ctx context.Context, arg GetRuneTransactio
|
||||
arg.RuneID,
|
||||
arg.RuneIDBlockHeight,
|
||||
arg.RuneIDTxIndex,
|
||||
arg.FromBlock,
|
||||
arg.ToBlock,
|
||||
arg.BlockHeight,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -62,7 +62,7 @@ func (r *Repository) GetIndexedBlockByHeight(ctx context.Context, height int64)
|
||||
return indexedBlock, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, fromBlock, toBlock uint64) ([]*entity.RuneTransaction, error) {
|
||||
func (r *Repository) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, height uint64) ([]*entity.RuneTransaction, error) {
|
||||
pkScriptParam := []byte(fmt.Sprintf(`[{"pkScript":"%s"}]`, hex.EncodeToString(pkScript)))
|
||||
runeIdParam := []byte(fmt.Sprintf(`[{"runeId":"%s"}]`, runeId.String()))
|
||||
rows, err := r.queries.GetRuneTransactions(ctx, gen.GetRuneTransactionsParams{
|
||||
@@ -75,8 +75,7 @@ func (r *Repository) GetRuneTransactions(ctx context.Context, pkScript []byte, r
|
||||
RuneIDBlockHeight: int32(runeId.BlockHeight),
|
||||
RuneIDTxIndex: int32(runeId.TxIndex),
|
||||
|
||||
FromBlock: int32(fromBlock),
|
||||
ToBlock: int32(toBlock),
|
||||
BlockHeight: int32(height),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, fromBlock, toBlock uint64) ([]*entity.RuneTransaction, error) {
|
||||
txs, err := u.runesDg.GetRuneTransactions(ctx, pkScript, runeId, fromBlock, toBlock)
|
||||
func (u *Usecase) GetRuneTransactions(ctx context.Context, pkScript []byte, runeId runes.RuneId, height uint64) ([]*entity.RuneTransaction, error) {
|
||||
txs, err := u.runesDg.GetRuneTransactions(ctx, pkScript, runeId, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetTransactionsByHeight")
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
)
|
||||
|
||||
type Contract interface {
|
||||
GetRawTransactionAndHeightByTxHash(ctx context.Context, txHash chainhash.Hash) (*wire.MsgTx, int64, error)
|
||||
GetTransactionByHash(ctx context.Context, txHash chainhash.Hash) (*types.Transaction, error)
|
||||
}
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
package btcutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxSupportedPkScriptSize is the maximum supported size of a pkScript.
|
||||
MaxSupportedPkScriptSize = 40
|
||||
)
|
||||
|
||||
// IsAddress returns whether or not the passed string is a valid bitcoin address and valid supported type.
|
||||
//
|
||||
// NetParams is optional. If provided, we only check for that network,
|
||||
// otherwise, we check for all supported networks.
|
||||
func IsAddress(address string, defaultNet ...*chaincfg.Params) bool {
|
||||
if len(address) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// If defaultNet is provided, we only check for that network.
|
||||
net, ok := utils.Optional(defaultNet)
|
||||
if ok {
|
||||
_, _, err := parseAddress(address, net)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Otherwise, we check for all supported networks.
|
||||
for _, net := range supportedNetworks {
|
||||
_, _, err := parseAddress(address, net)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: create GetAddressNetwork
|
||||
// check `Bech32HRPSegwit` prefix or netID for P2SH/P2PKH is equal to `PubKeyHashAddrID/ScriptHashAddrID`
|
||||
|
||||
// GetAddressType returns the address type of the passed address.
|
||||
func GetAddressType(address string, net *chaincfg.Params) (AddressType, error) {
|
||||
_, addrType, err := parseAddress(address, net)
|
||||
return addrType, errors.WithStack(err)
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
decoded btcutil.Address
|
||||
net *chaincfg.Params
|
||||
encoded string
|
||||
encodedType AddressType
|
||||
scriptPubKey [MaxSupportedPkScriptSize]byte
|
||||
scriptPubKeySize int
|
||||
}
|
||||
|
||||
// NewAddress creates a new address from the given address string.
|
||||
//
|
||||
// defaultNet is required if your address is P2SH or P2PKH (legacy or nested segwit)
|
||||
// If your address is P2WSH, P2WPKH or P2TR, defaultNet is not required.
|
||||
func NewAddress(address string, defaultNet ...*chaincfg.Params) Address {
|
||||
addr, err := SafeNewAddress(address, defaultNet...)
|
||||
if err != nil {
|
||||
logger.Panic("can't create parse address", slogx.Error(err), slogx.String("package", "btcutils"))
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// SafeNewAddress creates a new address from the given address string.
|
||||
// It returns an error if the address is invalid.
|
||||
//
|
||||
// defaultNet is required if your address is P2SH or P2PKH (legacy or nested segwit)
|
||||
// If your address is P2WSH, P2WPKH or P2TR, defaultNet is not required.
|
||||
func SafeNewAddress(address string, defaultNet ...*chaincfg.Params) (Address, error) {
|
||||
net := utils.DefaultOptional(defaultNet, &chaincfg.MainNetParams)
|
||||
|
||||
decoded, addrType, err := parseAddress(address, net)
|
||||
if err != nil {
|
||||
return Address{}, errors.Wrap(err, "can't parse address")
|
||||
}
|
||||
|
||||
scriptPubkey, err := txscript.PayToAddrScript(decoded)
|
||||
if err != nil {
|
||||
return Address{}, errors.Wrap(err, "can't get script pubkey")
|
||||
}
|
||||
|
||||
fixedPkScript := [MaxSupportedPkScriptSize]byte{}
|
||||
copy(fixedPkScript[:], scriptPubkey)
|
||||
return Address{
|
||||
decoded: decoded,
|
||||
net: net,
|
||||
encoded: decoded.EncodeAddress(),
|
||||
encodedType: addrType,
|
||||
scriptPubKey: fixedPkScript,
|
||||
scriptPubKeySize: len(scriptPubkey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the address string.
|
||||
func (a Address) String() string {
|
||||
return a.encoded
|
||||
}
|
||||
|
||||
// Type returns the address type.
|
||||
func (a Address) Type() AddressType {
|
||||
return a.encodedType
|
||||
}
|
||||
|
||||
// Decoded returns the btcutil.Address
|
||||
func (a Address) Decoded() btcutil.Address {
|
||||
return a.decoded
|
||||
}
|
||||
|
||||
// IsForNet returns whether or not the address is associated with the passed bitcoin network.
|
||||
func (a Address) IsForNet(net *chaincfg.Params) bool {
|
||||
return a.decoded.IsForNet(net)
|
||||
}
|
||||
|
||||
// ScriptAddress returns the raw bytes of the address to be used when inserting the address into a txout's script.
|
||||
func (a Address) ScriptAddress() []byte {
|
||||
return a.decoded.ScriptAddress()
|
||||
}
|
||||
|
||||
// Net returns the address network params.
|
||||
func (a Address) Net() *chaincfg.Params {
|
||||
return a.net
|
||||
}
|
||||
|
||||
// NetworkName
|
||||
func (a Address) NetworkName() string {
|
||||
return a.net.Name
|
||||
}
|
||||
|
||||
// ScriptPubKey or pubkey script
|
||||
func (a Address) ScriptPubKey() []byte {
|
||||
return a.scriptPubKey[:a.scriptPubKeySize]
|
||||
}
|
||||
|
||||
// Equal return true if addresses are equal
|
||||
func (a Address) Equal(b Address) bool {
|
||||
return a.encoded == b.encoded
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
func (a Address) MarshalText() ([]byte, error) {
|
||||
return []byte(a.encoded), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
func (a *Address) UnmarshalText(input []byte) error {
|
||||
address := string(input)
|
||||
addr, err := SafeNewAddress(address)
|
||||
if err == nil {
|
||||
*a = addr
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(errs.InvalidArgument, "invalid address `%s`", address)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (a Address) MarshalJSON() ([]byte, error) {
|
||||
t, err := a.MarshalText()
|
||||
if err != nil {
|
||||
return nil, &json.MarshalerError{Type: reflect.TypeOf(a), Err: err}
|
||||
}
|
||||
b := make([]byte, len(t)+2)
|
||||
b[0], b[len(b)-1] = '"', '"' // add quotes
|
||||
copy(b[1:], t)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses a hash in hex syntax.
|
||||
func (a *Address) UnmarshalJSON(input []byte) error {
|
||||
if !(len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"') {
|
||||
return &json.UnmarshalTypeError{Value: "non-string", Type: reflect.TypeOf(Address{})}
|
||||
}
|
||||
if err := a.UnmarshalText(input[1 : len(input)-1]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseAddress(address string, params *chaincfg.Params) (btcutil.Address, AddressType, error) {
|
||||
decoded, err := btcutil.DecodeAddress(address, params)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "can't decode address `%s` for network `%s`", address, params.Name)
|
||||
}
|
||||
|
||||
switch decoded.(type) {
|
||||
case *btcutil.AddressWitnessPubKeyHash:
|
||||
return decoded, AddressP2WPKH, nil
|
||||
case *btcutil.AddressTaproot:
|
||||
return decoded, AddressP2TR, nil
|
||||
case *btcutil.AddressScriptHash:
|
||||
return decoded, AddressP2SH, nil
|
||||
case *btcutil.AddressPubKeyHash:
|
||||
return decoded, AddressP2PKH, nil
|
||||
case *btcutil.AddressWitnessScriptHash:
|
||||
return decoded, AddressP2WSH, nil
|
||||
default:
|
||||
return nil, 0, errors.Wrap(errs.Unsupported, "unsupported address type")
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package btcutils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
)
|
||||
|
||||
/*
|
||||
NOTE:
|
||||
|
||||
# Compare this benchmark to go-ethereum/common.Address utils
|
||||
- go-ethereum/common.HexToAddress speed: 45 ns/op, 48 B/op, 1 allocs/op
|
||||
- go-ethereum/common.IsHexAddress speed: 25 ns/op, 0 B/op, 0 allocs/op
|
||||
|
||||
It's slower than go-ethereum/common.Address utils because ethereum wallet address is Hex string 20 bytes,
|
||||
but Bitcoin has many types of address and each type has complex algorithm to solve (can't solve and validate address type directly from address string)
|
||||
|
||||
20/Jan/2024 @Planxnx Macbook Air M1 16GB
|
||||
BenchmarkIsAddress/specific-network/mainnet/P2WPKH-8 1776146 625.6 ns/op 120 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/specific-network/testnet3/P2WPKH-8 1917876 623.2 ns/op 120 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/specific-network/mainnet/P2TR-8 1330348 915.4 ns/op 160 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/specific-network/testnet3/P2TR-8 1235806 931.1 ns/op 160 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/specific-network/mainnet/P2WSH-8 1261730 960.9 ns/op 160 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/specific-network/testnet3/P2WSH-8 1307851 916.1 ns/op 160 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/specific-network/mainnet/P2SH-8 3081762 402.0 ns/op 192 B/op 8 allocs/op
|
||||
BenchmarkIsAddress/specific-network/testnet3/P2SH-8 3245838 344.9 ns/op 176 B/op 7 allocs/op
|
||||
BenchmarkIsAddress/specific-network/mainnet/P2PKH-8 2904252 410.4 ns/op 184 B/op 8 allocs/op
|
||||
BenchmarkIsAddress/specific-network/testnet3/P2PKH-8 3522332 342.8 ns/op 176 B/op 7 allocs/op
|
||||
BenchmarkIsAddress/automate-network/mainnet/P2WPKH-8 1882059 637.6 ns/op 120 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/automate-network/testnet3/P2WPKH-8 1626151 664.8 ns/op 120 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/automate-network/mainnet/P2TR-8 1250253 952.1 ns/op 160 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/automate-network/testnet3/P2TR-8 1257901 993.7 ns/op 160 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/automate-network/mainnet/P2WSH-8 1000000 1005 ns/op 160 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/automate-network/testnet3/P2WSH-8 1209108 971.2 ns/op 160 B/op 3 allocs/op
|
||||
BenchmarkIsAddress/automate-network/mainnet/P2SH-8 1869075 625.0 ns/op 268 B/op 9 allocs/op
|
||||
BenchmarkIsAddress/automate-network/testnet3/P2SH-8 779496 1609 ns/op 694 B/op 17 allocs/op
|
||||
BenchmarkIsAddress/automate-network/mainnet/P2PKH-8 1924058 650.6 ns/op 259 B/op 9 allocs/op
|
||||
BenchmarkIsAddress/automate-network/testnet3/P2PKH-8 721510 1690 ns/op 694 B/op 17 allocs/op
|
||||
*/
|
||||
func BenchmarkIsAddress(b *testing.B) {
|
||||
cases := []btcutils.Address{
|
||||
/* P2WPKH */ btcutils.NewAddress("bc1qfpgdxtpl7kz5qdus2pmexyjaza99c28q8uyczh", &chaincfg.MainNetParams),
|
||||
/* P2WPKH */ btcutils.NewAddress("tb1qfpgdxtpl7kz5qdus2pmexyjaza99c28qd6ltey", &chaincfg.TestNet3Params),
|
||||
/* P2TR */ btcutils.NewAddress("bc1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qvz5d38", &chaincfg.MainNetParams),
|
||||
/* P2TR */ btcutils.NewAddress("tb1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qm2zztg", &chaincfg.TestNet3Params),
|
||||
/* P2WSH */ btcutils.NewAddress("bc1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak", &chaincfg.MainNetParams),
|
||||
/* P2WSH */ btcutils.NewAddress("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", &chaincfg.TestNet3Params),
|
||||
/* P2SH */ btcutils.NewAddress("3Ccte7SJz71tcssLPZy3TdWz5DTPeNRbPw", &chaincfg.MainNetParams),
|
||||
/* P2SH */ btcutils.NewAddress("2NCxMvHPTduZcCuUeAiWUpuwHga7Y66y9XJ", &chaincfg.TestNet3Params),
|
||||
/* P2PKH */ btcutils.NewAddress("1KrRZSShVkdc8J71CtY4wdw46Rx3BRLKyH", &chaincfg.MainNetParams),
|
||||
/* P2PKH */ btcutils.NewAddress("migbBPcDajPfffrhoLpYFTQNXQFbWbhpz3", &chaincfg.TestNet3Params),
|
||||
}
|
||||
|
||||
b.Run("specific-network", func(b *testing.B) {
|
||||
for _, c := range cases {
|
||||
b.Run(c.NetworkName()+"/"+c.Type().String(), func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = btcutils.IsAddress(c.String(), c.Net())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("automate-network", func(b *testing.B) {
|
||||
for _, c := range cases {
|
||||
b.Run(c.NetworkName()+"/"+c.Type().String(), func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ok := btcutils.IsAddress(c.String())
|
||||
if !ok {
|
||||
b.Error("IsAddress returned false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,449 +0,0 @@
|
||||
package btcutils_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetAddressType(t *testing.T) {
|
||||
type Spec struct {
|
||||
Address string
|
||||
DefaultNet *chaincfg.Params
|
||||
|
||||
ExpectedError error
|
||||
ExpectedAddressType btcutils.AddressType
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
Address: "bc1qfpgdxtpl7kz5qdus2pmexyjaza99c28q8uyczh",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2WPKH,
|
||||
},
|
||||
{
|
||||
Address: "tb1qfpgdxtpl7kz5qdus2pmexyjaza99c28qd6ltey",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2WPKH,
|
||||
},
|
||||
{
|
||||
Address: "bc1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qvz5d38",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2TR,
|
||||
},
|
||||
{
|
||||
Address: "tb1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qm2zztg",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2TR,
|
||||
},
|
||||
{
|
||||
Address: "3Ccte7SJz71tcssLPZy3TdWz5DTPeNRbPw",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2SH,
|
||||
},
|
||||
{
|
||||
Address: "1KrRZSShVkdc8J71CtY4wdw46Rx3BRLKyH",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2PKH,
|
||||
},
|
||||
{
|
||||
Address: "bc1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2WSH,
|
||||
},
|
||||
{
|
||||
Address: "migbBPcDajPfffrhoLpYFTQNXQFbWbhpz3",
|
||||
DefaultNet: &chaincfg.TestNet3Params,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2PKH,
|
||||
},
|
||||
{
|
||||
Address: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2WSH,
|
||||
},
|
||||
{
|
||||
Address: "2NCxMvHPTduZcCuUeAiWUpuwHga7Y66y9XJ",
|
||||
DefaultNet: &chaincfg.TestNet3Params,
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2SH,
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(fmt.Sprintf("address:%s", spec.Address), func(t *testing.T) {
|
||||
actualAddressType, actualError := btcutils.GetAddressType(spec.Address, spec.DefaultNet)
|
||||
if spec.ExpectedError != nil {
|
||||
assert.ErrorIs(t, actualError, spec.ExpectedError)
|
||||
} else {
|
||||
assert.Equal(t, spec.ExpectedAddressType, actualAddressType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAddress(t *testing.T) {
|
||||
type Spec struct {
|
||||
Address string
|
||||
DefaultNet *chaincfg.Params
|
||||
|
||||
ExpectedAddressType btcutils.AddressType
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
Address: "bc1qfpgdxtpl7kz5qdus2pmexyjaza99c28q8uyczh",
|
||||
// DefaultNet: &chaincfg.MainNetParams, // Optional
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2WPKH,
|
||||
},
|
||||
{
|
||||
Address: "tb1qfpgdxtpl7kz5qdus2pmexyjaza99c28qd6ltey",
|
||||
// DefaultNet: &chaincfg.MainNetParams, // Optional
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2WPKH,
|
||||
},
|
||||
{
|
||||
Address: "bc1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qvz5d38",
|
||||
// DefaultNet: &chaincfg.MainNetParams, // Optional
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2TR,
|
||||
},
|
||||
{
|
||||
Address: "tb1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qm2zztg",
|
||||
// DefaultNet: &chaincfg.MainNetParams, // Optional
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2TR,
|
||||
},
|
||||
{
|
||||
Address: "bc1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak",
|
||||
// DefaultNet: &chaincfg.MainNetParams, // Optional
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2WSH,
|
||||
},
|
||||
{
|
||||
Address: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
|
||||
// DefaultNet: &chaincfg.MainNetParams, // Optional
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2WSH,
|
||||
},
|
||||
{
|
||||
Address: "3Ccte7SJz71tcssLPZy3TdWz5DTPeNRbPw",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2SH,
|
||||
},
|
||||
{
|
||||
Address: "2NCxMvHPTduZcCuUeAiWUpuwHga7Y66y9XJ",
|
||||
DefaultNet: &chaincfg.TestNet3Params,
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2SH,
|
||||
},
|
||||
{
|
||||
Address: "1KrRZSShVkdc8J71CtY4wdw46Rx3BRLKyH",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2PKH,
|
||||
},
|
||||
{
|
||||
Address: "migbBPcDajPfffrhoLpYFTQNXQFbWbhpz3",
|
||||
DefaultNet: &chaincfg.TestNet3Params,
|
||||
|
||||
ExpectedAddressType: btcutils.AddressP2PKH,
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(fmt.Sprintf("address:%s,type:%s", spec.Address, spec.ExpectedAddressType), func(t *testing.T) {
|
||||
addr := btcutils.NewAddress(spec.Address, spec.DefaultNet)
|
||||
|
||||
assert.Equal(t, spec.ExpectedAddressType, addr.Type())
|
||||
assert.Equal(t, spec.Address, addr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAddress(t *testing.T) {
|
||||
type Spec struct {
|
||||
Address string
|
||||
Expected bool
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
Address: "bc1qfpgdxtpl7kz5qdus2pmexyjaza99c28q8uyczh",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "tb1qfpgdxtpl7kz5qdus2pmexyjaza99c28qd6ltey",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "bc1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qvz5d38",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "tb1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qm2zztg",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "bc1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "3Ccte7SJz71tcssLPZy3TdWz5DTPeNRbPw",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "2NCxMvHPTduZcCuUeAiWUpuwHga7Y66y9XJ",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "1KrRZSShVkdc8J71CtY4wdw46Rx3BRLKyH",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "migbBPcDajPfffrhoLpYFTQNXQFbWbhpz3",
|
||||
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Address: "",
|
||||
|
||||
Expected: false,
|
||||
},
|
||||
{
|
||||
Address: "migbBPcDajPfffrhoLpYFTQNXQFbWbhpz2",
|
||||
|
||||
Expected: false,
|
||||
},
|
||||
{
|
||||
Address: "bc1qfpgdxtpl7kz5qdus2pmexyjaza99c28q8uyczz",
|
||||
|
||||
Expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(fmt.Sprintf("address:%s", spec.Address), func(t *testing.T) {
|
||||
ok := btcutils.IsAddress(spec.Address)
|
||||
assert.Equal(t, spec.Expected, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddressEncoding(t *testing.T) {
|
||||
rawAddress := "bc1qfpgdxtpl7kz5qdus2pmexyjaza99c28q8uyczh"
|
||||
address := btcutils.NewAddress(rawAddress, &chaincfg.MainNetParams)
|
||||
|
||||
type Spec struct {
|
||||
Data interface{}
|
||||
Expected string
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
Data: address,
|
||||
Expected: fmt.Sprintf(`"%s"`, rawAddress),
|
||||
},
|
||||
{
|
||||
Data: map[string]interface{}{
|
||||
"address": rawAddress,
|
||||
},
|
||||
Expected: fmt.Sprintf(`{"address":"%s"}`, rawAddress),
|
||||
},
|
||||
}
|
||||
|
||||
for i, spec := range specs {
|
||||
t.Run(fmt.Sprint(i+1), func(t *testing.T) {
|
||||
actual, err := json.Marshal(spec.Data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, spec.Expected, string(actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddressDecoding(t *testing.T) {
|
||||
rawAddress := "bc1qfpgdxtpl7kz5qdus2pmexyjaza99c28q8uyczh"
|
||||
address := btcutils.NewAddress(rawAddress, &chaincfg.MainNetParams)
|
||||
|
||||
// Case #1: address is a string
|
||||
t.Run("from_string", func(t *testing.T) {
|
||||
input := fmt.Sprintf(`"%s"`, rawAddress)
|
||||
expected := address
|
||||
actual := btcutils.Address{}
|
||||
|
||||
err := json.Unmarshal([]byte(input), &actual)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
})
|
||||
|
||||
// Case #2: address is a field of a struct
|
||||
t.Run("from_field_string", func(t *testing.T) {
|
||||
type Data struct {
|
||||
Address btcutils.Address `json:"address"`
|
||||
}
|
||||
input := fmt.Sprintf(`{"address":"%s"}`, rawAddress)
|
||||
expected := Data{Address: address}
|
||||
actual := Data{}
|
||||
err := json.Unmarshal([]byte(input), &actual)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
})
|
||||
|
||||
// Case #3: address is an element of an array
|
||||
t.Run("from_array", func(t *testing.T) {
|
||||
input := fmt.Sprintf(`["%s"]`, rawAddress)
|
||||
expected := []btcutils.Address{address}
|
||||
actual := []btcutils.Address{}
|
||||
err := json.Unmarshal([]byte(input), &actual)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
})
|
||||
|
||||
// Case #4: not supported address type
|
||||
t.Run("from_string/not_address", func(t *testing.T) {
|
||||
input := fmt.Sprintf(`"%s"`, "THIS_IS_NOT_SUPPORTED_ADDRESS")
|
||||
actual := btcutils.Address{}
|
||||
err := json.Unmarshal([]byte(input), &actual)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
// Case #5: invalid field type
|
||||
t.Run("from_number", func(t *testing.T) {
|
||||
type Data struct {
|
||||
Address btcutils.Address `json:"address"`
|
||||
}
|
||||
input := fmt.Sprintf(`{"address":%d}`, 123)
|
||||
actual := Data{}
|
||||
err := json.Unmarshal([]byte(input), &actual)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddressPkScript(t *testing.T) {
|
||||
anyErr := errors.New("any error")
|
||||
type Spec struct {
|
||||
Address string
|
||||
DefaultNet *chaincfg.Params
|
||||
ExpectedError error
|
||||
ExpectedPkScript string // hex encoded
|
||||
}
|
||||
specs := []Spec{
|
||||
{
|
||||
Address: "some_invalid_address",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: anyErr,
|
||||
ExpectedPkScript: "",
|
||||
},
|
||||
{
|
||||
// P2WPKH
|
||||
Address: "bc1qdx72th7e3z8zc5wdrdxweswfcne974pjneyjln",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "001469bca5dfd9888e2c51cd1b4cecc1c9c4f25f5432",
|
||||
},
|
||||
{
|
||||
// P2WPKH
|
||||
Address: "bc1q7cj6gz6t3d28qg7kxhrc7h5t3h0re34fqqalga",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "0014f625a40b4b8b547023d635c78f5e8b8dde3cc6a9",
|
||||
},
|
||||
{
|
||||
// P2TR
|
||||
Address: "bc1pfd0zw2jwlpn4xckpr3dxpt7x0gw6wetuftxvrc4dt2qgn9azjuus65fug6",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "51204b5e272a4ef8675362c11c5a60afc67a1da7657c4accc1e2ad5a808997a29739",
|
||||
},
|
||||
{
|
||||
// P2TR
|
||||
Address: "bc1pxpumml545tqum5afarzlmnnez2npd35nvf0j0vnrp88nemqsn54qle05sm",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "51203079bdfe95a2c1cdd3a9e8c5fdce7912a616c693625f27b26309cf3cec109d2a",
|
||||
},
|
||||
{
|
||||
// P2SH
|
||||
Address: "3Ccte7SJz71tcssLPZy3TdWz5DTPeNRbPw",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "a91477e1a3d54f545d83869ae3a6b28b071422801d7b87",
|
||||
},
|
||||
{
|
||||
// P2PKH
|
||||
Address: "1KrRZSShVkdc8J71CtY4wdw46Rx3BRLKyH",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "76a914cecb25b53809991c7beef2d27bc2be49e78c684388ac",
|
||||
},
|
||||
{
|
||||
// P2WSH
|
||||
Address: "bc1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "0020cdbf909e935c855d3e8d1b61aeb9c5e3c03ae8021b286839b1a72f2e48fdba70",
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(spec.Address, func(t *testing.T) {
|
||||
addr, err := btcutils.SafeNewAddress(spec.Address, spec.DefaultNet)
|
||||
if spec.ExpectedError != nil {
|
||||
if errors.Is(spec.ExpectedError, anyErr) {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.ErrorIs(t, err, spec.ExpectedError)
|
||||
}
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, spec.ExpectedPkScript, hex.EncodeToString(addr.ScriptPubKey()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package btcutils
|
||||
|
||||
import (
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
)
|
||||
|
||||
var (
|
||||
// NullAddress is an address that script address is all zeros.
|
||||
NullAddress = NewAddress("1111111111111111111114oLvT2", &chaincfg.MainNetParams)
|
||||
|
||||
// NullHash is a hash that all bytes are zero.
|
||||
NullHash = utils.Must(chainhash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000"))
|
||||
)
|
||||
|
||||
// TransactionType is the type of bitcoin transaction
|
||||
// It's an alias of txscript.ScriptClass
|
||||
type TransactionType = txscript.ScriptClass
|
||||
|
||||
// AddressType is the type of bitcoin address.
|
||||
// It's an alias of txscript.ScriptClass
|
||||
type AddressType = txscript.ScriptClass
|
||||
|
||||
// Types of bitcoin transaction
|
||||
const (
|
||||
TransactionP2WPKH = txscript.WitnessV0PubKeyHashTy
|
||||
TransactionP2TR = txscript.WitnessV1TaprootTy
|
||||
TransactionTaproot = TransactionP2TR // Alias of P2TR
|
||||
TransactionP2SH = txscript.ScriptHashTy
|
||||
TransactionP2PKH = txscript.PubKeyHashTy
|
||||
TransactionP2WSH = txscript.WitnessV0ScriptHashTy
|
||||
)
|
||||
|
||||
// Types of bitcoin address
|
||||
const (
|
||||
AddressP2WPKH = txscript.WitnessV0PubKeyHashTy
|
||||
AddressP2TR = txscript.WitnessV1TaprootTy
|
||||
AddressTaproot = AddressP2TR // Alias of P2TR
|
||||
AddressP2SH = txscript.ScriptHashTy
|
||||
AddressP2PKH = txscript.PubKeyHashTy
|
||||
AddressP2WSH = txscript.WitnessV0ScriptHashTy
|
||||
)
|
||||
|
||||
// IsSupportType returns true if the given tx/address type is supported.
|
||||
func IsSupportType(t txscript.ScriptClass) bool {
|
||||
_, ok := supportedTypes[t]
|
||||
return ok
|
||||
}
|
||||
|
||||
var supportedTypes = map[txscript.ScriptClass]struct{}{
|
||||
txscript.WitnessV0PubKeyHashTy: {},
|
||||
txscript.WitnessV1TaprootTy: {},
|
||||
txscript.ScriptHashTy: {},
|
||||
txscript.PubKeyHashTy: {},
|
||||
txscript.WitnessV0ScriptHashTy: {},
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package btcutils
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
)
|
||||
|
||||
var supportedNetworks = map[string]*chaincfg.Params{
|
||||
"mainnet": &chaincfg.MainNetParams,
|
||||
"testnet": &chaincfg.TestNet3Params,
|
||||
}
|
||||
|
||||
// IsSupportedNetwork returns true if the given network is supported.
|
||||
//
|
||||
// TODO: create enum for network
|
||||
func IsSupportedNetwork(network string) bool {
|
||||
_, ok := supportedNetworks[network]
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetNetParams returns the *chaincfg.Params for the given network.
|
||||
func GetNetParams(network string) *chaincfg.Params {
|
||||
return supportedNetworks[network]
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package btcutils
|
||||
|
||||
import (
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
)
|
||||
|
||||
// NewPkScript creates a pubkey script(or witness program) from the given address string
|
||||
//
|
||||
// see: https://en.bitcoin.it/wiki/Script
|
||||
func NewPkScript(address string, defaultNet ...*chaincfg.Params) ([]byte, error) {
|
||||
net := utils.DefaultOptional(defaultNet, &chaincfg.MainNetParams)
|
||||
decoded, _, err := parseAddress(address, net)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't parse address")
|
||||
}
|
||||
scriptPubkey, err := txscript.PayToAddrScript(decoded)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't get script pubkey")
|
||||
}
|
||||
return scriptPubkey, nil
|
||||
}
|
||||
|
||||
// GetAddressTypeFromPkScript returns the address type from the given pubkey script/script pubkey.
|
||||
func GetAddressTypeFromPkScript(pkScript []byte, defaultNet ...*chaincfg.Params) (AddressType, error) {
|
||||
net := utils.DefaultOptional(defaultNet, &chaincfg.MainNetParams)
|
||||
scriptClass, _, _, err := txscript.ExtractPkScriptAddrs(pkScript, net)
|
||||
if err != nil {
|
||||
return txscript.NonStandardTy, errors.Wrap(err, "can't parse pkScript")
|
||||
}
|
||||
return scriptClass, nil
|
||||
}
|
||||
|
||||
// ExtractAddressFromPkScript extracts address from the given pubkey script/script pubkey.
|
||||
// multi-signature script not supported
|
||||
func ExtractAddressFromPkScript(pkScript []byte, defaultNet ...*chaincfg.Params) (Address, error) {
|
||||
if len(pkScript) == 0 {
|
||||
return Address{}, errors.New("empty pkScript")
|
||||
}
|
||||
if pkScript[0] == txscript.OP_RETURN {
|
||||
return Address{}, errors.Wrap(errs.NotSupported, "OP_RETURN script")
|
||||
}
|
||||
net := utils.DefaultOptional(defaultNet, &chaincfg.MainNetParams)
|
||||
addrType, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, net)
|
||||
if err != nil {
|
||||
return Address{}, errors.Wrap(err, "can't parse pkScript")
|
||||
}
|
||||
if !IsSupportType(addrType) {
|
||||
return Address{}, errors.Wrapf(errs.NotSupported, "unsupported pkscript type %s", addrType)
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
return Address{}, errors.New("can't extract address from pkScript")
|
||||
}
|
||||
|
||||
fixedPkScript := [MaxSupportedPkScriptSize]byte{}
|
||||
copy(fixedPkScript[:], pkScript)
|
||||
|
||||
return Address{
|
||||
decoded: addrs[0],
|
||||
net: net,
|
||||
encoded: addrs[0].EncodeAddress(),
|
||||
encodedType: addrType,
|
||||
scriptPubKey: fixedPkScript,
|
||||
scriptPubKeySize: len(pkScript),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
package btcutils_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewPkScript(t *testing.T) {
|
||||
anyError := errors.New("any error")
|
||||
|
||||
type Spec struct {
|
||||
Address string
|
||||
DefaultNet *chaincfg.Params
|
||||
ExpectedError error
|
||||
ExpectedPkScript string // hex encoded
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
Address: "some_invalid_address",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: anyError,
|
||||
ExpectedPkScript: "",
|
||||
},
|
||||
{
|
||||
// P2WPKH
|
||||
Address: "bc1qdx72th7e3z8zc5wdrdxweswfcne974pjneyjln",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "001469bca5dfd9888e2c51cd1b4cecc1c9c4f25f5432",
|
||||
},
|
||||
{
|
||||
// P2WPKH
|
||||
Address: "bc1q7cj6gz6t3d28qg7kxhrc7h5t3h0re34fqqalga",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "0014f625a40b4b8b547023d635c78f5e8b8dde3cc6a9",
|
||||
},
|
||||
{
|
||||
// P2TR
|
||||
Address: "bc1pfd0zw2jwlpn4xckpr3dxpt7x0gw6wetuftxvrc4dt2qgn9azjuus65fug6",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "51204b5e272a4ef8675362c11c5a60afc67a1da7657c4accc1e2ad5a808997a29739",
|
||||
},
|
||||
{
|
||||
// P2TR
|
||||
Address: "bc1pxpumml545tqum5afarzlmnnez2npd35nvf0j0vnrp88nemqsn54qle05sm",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "51203079bdfe95a2c1cdd3a9e8c5fdce7912a616c693625f27b26309cf3cec109d2a",
|
||||
},
|
||||
{
|
||||
// P2SH
|
||||
Address: "3Ccte7SJz71tcssLPZy3TdWz5DTPeNRbPw",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "a91477e1a3d54f545d83869ae3a6b28b071422801d7b87",
|
||||
},
|
||||
{
|
||||
// P2PKH
|
||||
Address: "1KrRZSShVkdc8J71CtY4wdw46Rx3BRLKyH",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "76a914cecb25b53809991c7beef2d27bc2be49e78c684388ac",
|
||||
},
|
||||
{
|
||||
// P2WSH
|
||||
Address: "bc1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedError: nil,
|
||||
ExpectedPkScript: "0020cdbf909e935c855d3e8d1b61aeb9c5e3c03ae8021b286839b1a72f2e48fdba70",
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(fmt.Sprintf("address:%s", spec.Address), func(t *testing.T) {
|
||||
// Validate Expected PkScript
|
||||
if spec.ExpectedError == nil {
|
||||
{
|
||||
expectedPkScriptRaw, err := hex.DecodeString(spec.ExpectedPkScript)
|
||||
if err != nil {
|
||||
t.Fatalf("can't decode expected pkscript %s, Reason: %s", spec.ExpectedPkScript, err)
|
||||
}
|
||||
expectedPkScript, err := txscript.ParsePkScript(expectedPkScriptRaw)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid expected pkscript %s, Reason: %s", spec.ExpectedPkScript, err)
|
||||
}
|
||||
|
||||
expectedAddress, err := expectedPkScript.Address(spec.DefaultNet)
|
||||
if err != nil {
|
||||
t.Fatalf("can't get address from expected pkscript %s, Reason: %s", spec.ExpectedPkScript, err)
|
||||
}
|
||||
assert.Equal(t, spec.Address, expectedAddress.EncodeAddress())
|
||||
}
|
||||
{
|
||||
address, err := btcutil.DecodeAddress(spec.Address, spec.DefaultNet)
|
||||
if err != nil {
|
||||
t.Fatalf("can't decode address %s(%s),Reason: %s", spec.Address, spec.DefaultNet.Name, err)
|
||||
}
|
||||
|
||||
pkScript, err := txscript.PayToAddrScript(address)
|
||||
if err != nil {
|
||||
t.Fatalf("can't get pkscript from address %s(%s),Reason: %s", spec.Address, spec.DefaultNet.Name, err)
|
||||
}
|
||||
|
||||
pkScriptStr := hex.EncodeToString(pkScript)
|
||||
assert.Equal(t, spec.ExpectedPkScript, pkScriptStr)
|
||||
}
|
||||
}
|
||||
|
||||
pkScript, err := btcutils.NewPkScript(spec.Address, spec.DefaultNet)
|
||||
if spec.ExpectedError == anyError {
|
||||
assert.Error(t, err)
|
||||
} else if spec.ExpectedError != nil {
|
||||
assert.ErrorIs(t, err, spec.ExpectedError)
|
||||
} else {
|
||||
address, err := btcutils.SafeNewAddress(spec.Address, spec.DefaultNet)
|
||||
if err != nil {
|
||||
t.Fatalf("can't create address %s(%s),Reason: %s", spec.Address, spec.DefaultNet.Name, err)
|
||||
}
|
||||
|
||||
// ScriptPubKey from address and from NewPkScript should be the same
|
||||
assert.Equal(t, address.ScriptPubKey(), pkScript)
|
||||
|
||||
// Expected PkScript and New PkScript should be the same
|
||||
pkScriptStr := hex.EncodeToString(pkScript)
|
||||
assert.Equal(t, spec.ExpectedPkScript, pkScriptStr)
|
||||
|
||||
// Can convert PkScript back to same address
|
||||
acualPkScript, err := txscript.ParsePkScript(address.ScriptPubKey())
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
assert.Equal(t, address.Decoded().String(), utils.Must(acualPkScript.Address(spec.DefaultNet)).String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAddressTypeFromPkScript(t *testing.T) {
|
||||
type Spec struct {
|
||||
PubkeyScript string
|
||||
|
||||
ExpectedError error
|
||||
ExpectedAddressType btcutils.AddressType
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
PubkeyScript: "0014602181cc89f7c9f54cb6d7607a3445e3e022895d",
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2WPKH,
|
||||
},
|
||||
{
|
||||
PubkeyScript: "5120ef8d59038dd51093fbfff794f658a07a3697b94d9e6d24e45b28abd88f10e33d",
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2TR,
|
||||
},
|
||||
{
|
||||
PubkeyScript: "a91416eef7e84fb9821db1341b6ccef1c4a4e5ec21e487",
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2SH,
|
||||
},
|
||||
{
|
||||
PubkeyScript: "76a914cecb25b53809991c7beef2d27bc2be49e78c684388ac",
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2PKH,
|
||||
},
|
||||
{
|
||||
PubkeyScript: "0020cdbf909e935c855d3e8d1b61aeb9c5e3c03ae8021b286839b1a72f2e48fdba70",
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2WSH,
|
||||
},
|
||||
{
|
||||
PubkeyScript: "0020cdbf909e935c855d3e8d1b61aeb9c5e3c03ae8021b286839b1a72f2e48fdba70",
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: btcutils.AddressP2WSH,
|
||||
},
|
||||
{
|
||||
PubkeyScript: "6a5d0614c0a2331441",
|
||||
|
||||
ExpectedError: nil,
|
||||
ExpectedAddressType: txscript.NonStandardTy,
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(fmt.Sprintf("PkScript:%s", spec.PubkeyScript), func(t *testing.T) {
|
||||
pkScript, err := hex.DecodeString(spec.PubkeyScript)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
actualAddressType, actualError := btcutils.GetAddressTypeFromPkScript(pkScript)
|
||||
if spec.ExpectedError != nil {
|
||||
assert.ErrorIs(t, actualError, spec.ExpectedError)
|
||||
} else {
|
||||
assert.Equal(t, spec.ExpectedAddressType, actualAddressType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package psbtutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
)
|
||||
|
||||
const (
|
||||
// default psbt encoding is hex
|
||||
DefaultEncoding = EncodingHex
|
||||
)
|
||||
|
||||
type Encoding string
|
||||
|
||||
const (
|
||||
EncodingBase64 Encoding = "base64"
|
||||
EncodingHex Encoding = "hex"
|
||||
)
|
||||
|
||||
// DecodeString decodes a psbt hex/base64 string into a psbt.Packet
|
||||
//
|
||||
// encoding is optional, default is EncodingHex
|
||||
func DecodeString(psbtStr string, encoding ...Encoding) (*psbt.Packet, error) {
|
||||
pC, err := Decode([]byte(psbtStr), encoding...)
|
||||
return pC, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// Decode decodes a psbt hex/base64 byte into a psbt.Packet
|
||||
//
|
||||
// encoding is optional, default is EncodingHex
|
||||
func Decode(psbtB []byte, encoding ...Encoding) (*psbt.Packet, error) {
|
||||
enc, ok := utils.Optional(encoding)
|
||||
if !ok {
|
||||
enc = DefaultEncoding
|
||||
}
|
||||
|
||||
var (
|
||||
psbtBytes []byte
|
||||
err error
|
||||
)
|
||||
|
||||
switch enc {
|
||||
case EncodingBase64, "b64":
|
||||
psbtBytes = make([]byte, base64.StdEncoding.DecodedLen(len(psbtB)))
|
||||
_, err = base64.StdEncoding.Decode(psbtBytes, psbtB)
|
||||
case EncodingHex:
|
||||
psbtBytes = make([]byte, hex.DecodedLen(len(psbtB)))
|
||||
_, err = hex.Decode(psbtBytes, psbtB)
|
||||
default:
|
||||
return nil, errors.Wrap(errs.Unsupported, "invalid encoding")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't decode psbt string")
|
||||
}
|
||||
|
||||
pC, err := psbt.NewFromRawBytes(bytes.NewReader(psbtBytes), false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't create psbt from given psbt")
|
||||
}
|
||||
|
||||
return pC, nil
|
||||
}
|
||||
|
||||
// EncodeToString encodes a psbt.Packet into a psbt hex/base64 string
|
||||
//
|
||||
// encoding is optional, default is EncodingHex
|
||||
func EncodeToString(pC *psbt.Packet, encoding ...Encoding) (string, error) {
|
||||
enc, ok := utils.Optional(encoding)
|
||||
if !ok {
|
||||
enc = DefaultEncoding
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pC.Serialize(&buf); err != nil {
|
||||
return "", errors.Wrap(err, "can't serialize psbt")
|
||||
}
|
||||
|
||||
switch enc {
|
||||
case EncodingBase64, "b64":
|
||||
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
|
||||
case EncodingHex:
|
||||
return hex.EncodeToString(buf.Bytes()), nil
|
||||
default:
|
||||
return "", errors.Wrap(errs.Unsupported, "invalid encoding")
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package psbtutils
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
)
|
||||
|
||||
// TxFee returns satoshis fee of a transaction given the fee rate (sat/vB)
|
||||
// and the number of inputs and outputs.
|
||||
func TxFee(feeRate int64, p *psbt.Packet) (int64, error) {
|
||||
size, err := PSBTSize(p)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "psbt size")
|
||||
}
|
||||
return int64(math.Ceil(size * float64(feeRate))), nil
|
||||
}
|
||||
|
||||
func PredictTxFee(feeRate int64, inputs, outputs int) int64 {
|
||||
/**
|
||||
TODO: handle edge cases like:
|
||||
1. when we predict that we need to use unnecessary UTXOs
|
||||
2. when we predict that we need to use more value than user have, but user do have enough for the actual transaction
|
||||
|
||||
Idea for solving this:
|
||||
- When trying to find the best UTXOs to use, we:
|
||||
- Will not reject when user's balance is not enough, instead we will return all UTXOs even if it's not enough.
|
||||
- Will be okay returning excessive UTXOs (say we predict we need 10K satoshis, but actually we only need 5K satoshis, then we will return UTXOs enough for 10K satoshis)
|
||||
- And then we:
|
||||
- Construct the actual PSBT, then select UTXOs to use accordingly,
|
||||
- If the user's balance is not enough, then we will return an error,
|
||||
- Or if when we predict we expect to use more UTXOs than the actual transaction, then we will just use what's needed.
|
||||
*/
|
||||
size := defaultOverhead + 148*float64(inputs) + 43*float64(outputs)
|
||||
return int64(math.Ceil(size * float64(feeRate)))
|
||||
}
|
||||
|
||||
type txSize struct {
|
||||
Overhead float64
|
||||
Inputs float64
|
||||
Outputs float64
|
||||
}
|
||||
|
||||
const defaultOverhead = 10.5
|
||||
|
||||
// Transaction Virtual Sizes Bytes
|
||||
//
|
||||
// Reference: https://bitcoinops.org/en/tools/calc-size/
|
||||
var txSizes = map[btcutils.TransactionType]txSize{
|
||||
btcutils.TransactionP2WPKH: {
|
||||
Inputs: 68,
|
||||
Outputs: 31,
|
||||
},
|
||||
btcutils.TransactionP2TR: {
|
||||
Inputs: 57.5,
|
||||
Outputs: 43,
|
||||
},
|
||||
btcutils.TransactionP2SH: {
|
||||
Inputs: 91,
|
||||
Outputs: 32,
|
||||
},
|
||||
btcutils.TransactionP2PKH: {
|
||||
Inputs: 148,
|
||||
Outputs: 34,
|
||||
},
|
||||
btcutils.TransactionP2WSH: {
|
||||
Inputs: 104.5,
|
||||
Outputs: 43,
|
||||
},
|
||||
}
|
||||
|
||||
func PSBTSize(psbt *psbt.Packet) (float64, error) {
|
||||
if err := psbt.SanityCheck(); err != nil {
|
||||
return 0, errors.Wrap(errors.Join(err, errs.InvalidArgument), "psbt sanity check")
|
||||
}
|
||||
|
||||
inputs := map[btcutils.TransactionType]int{}
|
||||
outputs := map[btcutils.TransactionType]int{}
|
||||
|
||||
for _, input := range psbt.Inputs {
|
||||
addrType, err := btcutils.GetAddressTypeFromPkScript(input.WitnessUtxo.PkScript)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "get address type from pk script")
|
||||
}
|
||||
inputs[addrType]++
|
||||
}
|
||||
|
||||
for _, output := range psbt.UnsignedTx.TxOut {
|
||||
addrType, err := btcutils.GetAddressTypeFromPkScript(output.PkScript)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "get address type from pk script")
|
||||
}
|
||||
outputs[addrType]++
|
||||
}
|
||||
|
||||
totalSize := defaultOverhead
|
||||
for txType, txSizeData := range txSizes {
|
||||
if inputCount, ok := inputs[txType]; ok {
|
||||
totalSize += txSizeData.Inputs * float64(inputCount)
|
||||
}
|
||||
if outputCount, ok := outputs[txType]; ok {
|
||||
totalSize += txSizeData.Outputs * float64(outputCount)
|
||||
}
|
||||
}
|
||||
|
||||
return totalSize, nil
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package psbtutils_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils/psbtutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPSBTSize(t *testing.T) {
|
||||
type Spec struct {
|
||||
Name string
|
||||
PSBTString string
|
||||
ExpectedError error
|
||||
ExpectedSize float64
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
Name: "3-inputs-3-outputs-taproot",
|
||||
PSBTString: "70736274ff0100fd06010100000003866c72cfeef533940eaee49b68778e6223914ea671411ec387bdb61f620889910000000000ffffffff866c72cfeef533940eaee49b68778e6223914ea671411ec387bdb61f620889910100000000ffffffff866c72cfeef533940eaee49b68778e6223914ea671411ec387bdb61f620889910200000000ffffffff03b0040000000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f22020000000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f4d370f00000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f000000000001012b58020000000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f0001012b58020000000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f0001012bcb3c0f00000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f00000000",
|
||||
ExpectedError: nil,
|
||||
ExpectedSize: 312,
|
||||
},
|
||||
{
|
||||
Name: "mixed-segwit-taproot",
|
||||
PSBTString: "70736274ff0100fd230202000000061f34960fef4e73c3c4c023f303c16e06f0eebb268bc0d3bac99fa78c031a45b90300000000ffffffff1f34960fef4e73c3c4c023f303c16e06f0eebb268bc0d3bac99fa78c031a45b90400000000ffffffff21c8ec368f2aff1a7baf4964e4070f52e7247ae39edfbda3976f8df4da1b72a00000000000ffffffff969e65b705e3d5071f1743a63381b3aa1ec31e1dbbbd63ab594a19ca399a58af0000000000ffffffffcca5cfd28bd6c54a851d97d029560b3047f7c6482fda7b2f2603d56ade8c95890000000000ffffffff1f34960fef4e73c3c4c023f303c16e06f0eebb268bc0d3bac99fa78c031a45b90500000000ffffffff0908070000000000001600144850d32c3ff585403790507793125d174a5c28e022020000000000001600144850d32c3ff585403790507793125d174a5c28e022020000000000001600144850d32c3ff585403790507793125d174a5c28e0b03600000000000016001459805fc1fdb9f05e190db569987c95c4f9deaa532a680000000000002251203a9ddeb6a2a327fed0f50d18778b28168e3ddb7fdfd4b05f4e438c9174d76a8d58020000000000001600144850d32c3ff585403790507793125d174a5c28e058020000000000001600144850d32c3ff585403790507793125d174a5c28e058020000000000001600144850d32c3ff585403790507793125d174a5c28e0b21f1e00000000001600144850d32c3ff585403790507793125d174a5c28e0000000000001011f58020000000000001600144850d32c3ff585403790507793125d174a5c28e00001011f58020000000000001600144850d32c3ff585403790507793125d174a5c28e00001011f58020000000000001600144850d32c3ff585403790507793125d174a5c28e00001011f220200000000000016001459805fc1fdb9f05e190db569987c95c4f9deaa53010304830000000001012b22020000000000002251203a9ddeb6a2a327fed0f50d18778b28168e3ddb7fdfd4b05f4e438c9174d76a8d010304830000000001011f06432000000000001600144850d32c3ff585403790507793125d174a5c28e000000000000000000000",
|
||||
ExpectedError: nil,
|
||||
ExpectedSize: 699,
|
||||
},
|
||||
{
|
||||
Name: "segwit-transfer-to-legacy",
|
||||
PSBTString: "70736274ff010074020000000124ba4becfc732f3b4729784a3dd0cc2494ae890d826377fd98aeb0607feb1ace0100000000ffffffff0210270000000000001976a91422bae94117be666b593916527d55bdaf030d756e88ac25f62e000000000016001476d1e072c9b8a18fa1e4be697c175e0c642026ac000000000001011fc51d2f000000000016001476d1e072c9b8a18fa1e4be697c175e0c642026ac01086b024730440220759df9d109298a1ef69b9faa1786f4118f0d4d63a68cd2061e217b6090573f62022053ffa117fc21e5bf20e7d16bb786de52dc0214c9a21af87b4e92a639ef66e997012103e0cb213a46a68b1f463a4858635ee44694ce4b512788833d629840341b1219c9000000",
|
||||
ExpectedError: nil,
|
||||
ExpectedSize: 143.5,
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(spec.Name, func(t *testing.T) {
|
||||
p, err := psbtutils.DecodeString(spec.PSBTString)
|
||||
assert.NoError(t, err)
|
||||
size, err := psbtutils.PSBTSize(p)
|
||||
if spec.ExpectedError != nil {
|
||||
assert.ErrorIs(t, err, spec.ExpectedError)
|
||||
} else {
|
||||
assert.Equal(t, spec.ExpectedSize, size)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPredictTxFee(t *testing.T) {
|
||||
type Spec struct {
|
||||
FeeRate int64
|
||||
InputsCount int
|
||||
OutputsCount int
|
||||
ExpectedFee int64
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
FeeRate: 100,
|
||||
InputsCount: 1,
|
||||
OutputsCount: 1,
|
||||
ExpectedFee: int64(math.Ceil((10.5 + 148 + 43) * 100)),
|
||||
},
|
||||
{
|
||||
FeeRate: 1,
|
||||
InputsCount: 99,
|
||||
OutputsCount: 99,
|
||||
ExpectedFee: int64(math.Ceil((10.5 + (99 * 148) + (99 * 43)) * 1)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(fmt.Sprintf("feeRate=%d:inputs=%d:outputs=%d", spec.FeeRate, spec.InputsCount, spec.OutputsCount), func(t *testing.T) {
|
||||
fee := psbtutils.PredictTxFee(spec.FeeRate, spec.InputsCount, spec.OutputsCount)
|
||||
assert.Equal(t, spec.ExpectedFee, fee)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxFee(t *testing.T) {
|
||||
type Spec struct {
|
||||
Name string
|
||||
FeeRate int64
|
||||
PSBTString string
|
||||
ExpectedError error
|
||||
ExpectedFee int64
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
Name: "3-inputs-3-outputs-taproot",
|
||||
FeeRate: 10,
|
||||
PSBTString: "70736274ff0100fd06010100000003866c72cfeef533940eaee49b68778e6223914ea671411ec387bdb61f620889910000000000ffffffff866c72cfeef533940eaee49b68778e6223914ea671411ec387bdb61f620889910100000000ffffffff866c72cfeef533940eaee49b68778e6223914ea671411ec387bdb61f620889910200000000ffffffff03b0040000000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f22020000000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f4d370f00000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f000000000001012b58020000000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f0001012b58020000000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f0001012bcb3c0f00000000002251205b954b2f91ded08c553551037bc71265a69a7586855ba4fdcf785a2494f0c37f00000000",
|
||||
ExpectedError: nil,
|
||||
ExpectedFee: 312 * 10,
|
||||
},
|
||||
{
|
||||
Name: "mixed-segwit-taproot",
|
||||
FeeRate: 20,
|
||||
PSBTString: "70736274ff0100fd230202000000061f34960fef4e73c3c4c023f303c16e06f0eebb268bc0d3bac99fa78c031a45b90300000000ffffffff1f34960fef4e73c3c4c023f303c16e06f0eebb268bc0d3bac99fa78c031a45b90400000000ffffffff21c8ec368f2aff1a7baf4964e4070f52e7247ae39edfbda3976f8df4da1b72a00000000000ffffffff969e65b705e3d5071f1743a63381b3aa1ec31e1dbbbd63ab594a19ca399a58af0000000000ffffffffcca5cfd28bd6c54a851d97d029560b3047f7c6482fda7b2f2603d56ade8c95890000000000ffffffff1f34960fef4e73c3c4c023f303c16e06f0eebb268bc0d3bac99fa78c031a45b90500000000ffffffff0908070000000000001600144850d32c3ff585403790507793125d174a5c28e022020000000000001600144850d32c3ff585403790507793125d174a5c28e022020000000000001600144850d32c3ff585403790507793125d174a5c28e0b03600000000000016001459805fc1fdb9f05e190db569987c95c4f9deaa532a680000000000002251203a9ddeb6a2a327fed0f50d18778b28168e3ddb7fdfd4b05f4e438c9174d76a8d58020000000000001600144850d32c3ff585403790507793125d174a5c28e058020000000000001600144850d32c3ff585403790507793125d174a5c28e058020000000000001600144850d32c3ff585403790507793125d174a5c28e0b21f1e00000000001600144850d32c3ff585403790507793125d174a5c28e0000000000001011f58020000000000001600144850d32c3ff585403790507793125d174a5c28e00001011f58020000000000001600144850d32c3ff585403790507793125d174a5c28e00001011f58020000000000001600144850d32c3ff585403790507793125d174a5c28e00001011f220200000000000016001459805fc1fdb9f05e190db569987c95c4f9deaa53010304830000000001012b22020000000000002251203a9ddeb6a2a327fed0f50d18778b28168e3ddb7fdfd4b05f4e438c9174d76a8d010304830000000001011f06432000000000001600144850d32c3ff585403790507793125d174a5c28e000000000000000000000",
|
||||
ExpectedError: nil,
|
||||
ExpectedFee: 699 * 20,
|
||||
},
|
||||
{
|
||||
Name: "segwit-transfer-to-legacy",
|
||||
FeeRate: 99,
|
||||
PSBTString: "70736274ff010074020000000124ba4becfc732f3b4729784a3dd0cc2494ae890d826377fd98aeb0607feb1ace0100000000ffffffff0210270000000000001976a91422bae94117be666b593916527d55bdaf030d756e88ac25f62e000000000016001476d1e072c9b8a18fa1e4be697c175e0c642026ac000000000001011fc51d2f000000000016001476d1e072c9b8a18fa1e4be697c175e0c642026ac01086b024730440220759df9d109298a1ef69b9faa1786f4118f0d4d63a68cd2061e217b6090573f62022053ffa117fc21e5bf20e7d16bb786de52dc0214c9a21af87b4e92a639ef66e997012103e0cb213a46a68b1f463a4858635ee44694ce4b512788833d629840341b1219c9000000",
|
||||
ExpectedError: nil,
|
||||
ExpectedFee: int64(math.Ceil((143.5) * 99)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(spec.Name, func(t *testing.T) {
|
||||
p, err := psbtutils.DecodeString(spec.PSBTString)
|
||||
assert.NoError(t, err)
|
||||
fee, err := psbtutils.TxFee(spec.FeeRate, p)
|
||||
if spec.ExpectedError != nil {
|
||||
assert.ErrorIs(t, err, spec.ExpectedError)
|
||||
} else {
|
||||
assert.Equal(t, spec.ExpectedFee, fee)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package psbtutils
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func IsReadyPSBT(pC *psbt.Packet, feeRate int64) (bool, error) {
|
||||
// if input = output + fee then it's ready
|
||||
|
||||
// Calculate tx fee
|
||||
fee, err := TxFee(feeRate, pC)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "calculate fee")
|
||||
}
|
||||
|
||||
// sum total input and output
|
||||
totalInputValue := lo.SumBy(pC.Inputs, func(input psbt.PInput) int64 { return input.WitnessUtxo.Value })
|
||||
totalOutputValue := lo.SumBy(pC.UnsignedTx.TxOut, func(txout *wire.TxOut) int64 { return txout.Value }) + fee
|
||||
|
||||
// it's perfect match
|
||||
if totalInputValue == totalOutputValue {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// if input is more than output + fee but not more than 1000 satoshi,
|
||||
// then it's ready
|
||||
if totalInputValue > totalOutputValue && totalInputValue-totalOutputValue < 1000 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package btcutils
|
||||
|
||||
import (
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
verifier "github.com/bitonicnl/verify-signed-message/pkg"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
func VerifySignature(address string, message string, sigBase64 string, defaultNet ...*chaincfg.Params) error {
|
||||
net := utils.DefaultOptional(defaultNet, &chaincfg.MainNetParams)
|
||||
_, err := verifier.VerifyWithChain(verifier.SignedMessage{
|
||||
Address: address,
|
||||
Message: message,
|
||||
Signature: sigBase64,
|
||||
}, net)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package btcutils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVerifySignature(t *testing.T) {
|
||||
{
|
||||
message := "Test123"
|
||||
address := "18J72YSM9pKLvyXX1XAjFXA98zeEvxBYmw"
|
||||
signature := "Gzhfsw0ItSrrTCChykFhPujeTyAcvVxiXwywxpHmkwFiKuUR2ETbaoFcocmcSshrtdIjfm8oXlJoTOLosZp3Yc8="
|
||||
network := &chaincfg.MainNetParams
|
||||
|
||||
err := VerifySignature(address, message, signature, network)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
{
|
||||
address := "tb1qr97cuq4kvq7plfetmxnl6kls46xaka78n2288z"
|
||||
message := "The outage comes at a time when bitcoin has been fast approaching new highs not seen since June 26, 2019."
|
||||
signature := "H/bSByRH7BW1YydfZlEx9x/nt4EAx/4A691CFlK1URbPEU5tJnTIu4emuzkgZFwC0ptvKuCnyBThnyLDCqPqT10="
|
||||
network := &chaincfg.TestNet3Params
|
||||
|
||||
err := VerifySignature(address, message, signature, network)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
{
|
||||
// Missmatch address
|
||||
address := "tb1qp7y2ywgrv8a4t9h47yphtgj8w759rk6vgd9ran"
|
||||
message := "The outage comes at a time when bitcoin has been fast approaching new highs not seen since June 26, 2019."
|
||||
signature := "H/bSByRH7BW1YydfZlEx9x/nt4EAx/4A691CFlK1URbPEU5tJnTIu4emuzkgZFwC0ptvKuCnyBThnyLDCqPqT10="
|
||||
network := &chaincfg.TestNet3Params
|
||||
|
||||
err := VerifySignature(address, message, signature, network)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
{
|
||||
// Missmatch signature
|
||||
address := "tb1qr97cuq4kvq7plfetmxnl6kls46xaka78n2288z"
|
||||
message := "The outage comes at a time when bitcoin has been fast approaching new highs not seen since June 26, 2019."
|
||||
signature := "Gzhfsw0ItSrrTCChykFhPujeTyAcvVxiXwywxpHmkwFiKuUR2ETbaoFcocmcSshrtdIjfm8oXlJoTOLosZp3Yc8="
|
||||
network := &chaincfg.TestNet3Params
|
||||
|
||||
err := VerifySignature(address, message, signature, network)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
{
|
||||
// Missmatch message
|
||||
address := "tb1qr97cuq4kvq7plfetmxnl6kls46xaka78n2288z"
|
||||
message := "Hello World"
|
||||
signature := "H/bSByRH7BW1YydfZlEx9x/nt4EAx/4A691CFlK1URbPEU5tJnTIu4emuzkgZFwC0ptvKuCnyBThnyLDCqPqT10="
|
||||
network := &chaincfg.TestNet3Params
|
||||
|
||||
err := VerifySignature(address, message, signature, network)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
{
|
||||
// Missmatch network
|
||||
address := "tb1qr97cuq4kvq7plfetmxnl6kls46xaka78n2288z"
|
||||
message := "The outage comes at a time when bitcoin has been fast approaching new highs not seen since June 26, 2019."
|
||||
signature := "H/bSByRH7BW1YydfZlEx9x/nt4EAx/4A691CFlK1URbPEU5tJnTIu4emuzkgZFwC0ptvKuCnyBThnyLDCqPqT10="
|
||||
network := &chaincfg.MainNetParams
|
||||
|
||||
err := VerifySignature(address, message, signature, network)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package btcutils
|
||||
|
||||
const (
|
||||
// TxVersion is the current latest supported transaction version.
|
||||
TxVersion = 2
|
||||
|
||||
// MaxTxInSequenceNum is the maximum sequence number the sequence field
|
||||
// of a transaction input can be.
|
||||
MaxTxInSequenceNum uint32 = 0xffffffff
|
||||
)
|
||||
@@ -10,28 +10,23 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// New setup error handler middleware
|
||||
func New() fiber.Handler {
|
||||
return func(ctx *fiber.Ctx) error {
|
||||
err := ctx.Next()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
func NewHTTPErrorHandler() func(ctx *fiber.Ctx, err error) error {
|
||||
return func(ctx *fiber.Ctx, err error) error {
|
||||
if e := new(errs.PublicError); errors.As(err, &e) {
|
||||
return errors.WithStack(ctx.Status(http.StatusBadRequest).JSON(fiber.Map{
|
||||
return errors.WithStack(ctx.Status(http.StatusBadRequest).JSON(map[string]any{
|
||||
"error": e.Message(),
|
||||
}))
|
||||
}
|
||||
if e := new(fiber.Error); errors.As(err, &e) {
|
||||
return errors.WithStack(ctx.Status(e.Code).JSON(fiber.Map{
|
||||
"error": e.Error(),
|
||||
}))
|
||||
return errors.WithStack(ctx.Status(e.Code).SendString(e.Error()))
|
||||
}
|
||||
logger.ErrorContext(ctx.UserContext(), "Something went wrong, api error",
|
||||
slogx.String("event", "api_error"),
|
||||
|
||||
logger.ErrorContext(ctx.UserContext(), "Something went wrong, unhandled api error",
|
||||
slogx.String("event", "api_unhandled_error"),
|
||||
slogx.Error(err),
|
||||
)
|
||||
return errors.WithStack(ctx.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
||||
|
||||
return errors.WithStack(ctx.Status(http.StatusInternalServerError).JSON(map[string]any{
|
||||
"error": "Internal Server Error",
|
||||
}))
|
||||
}
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
@@ -23,14 +24,13 @@ type Config struct {
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
baseURL *url.URL
|
||||
baseURL string
|
||||
Config
|
||||
}
|
||||
|
||||
func New(baseURL string, config ...Config) (*Client, error) {
|
||||
parsedBaseURL, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't parse base url")
|
||||
if _, err := url.Parse(baseURL); err != nil {
|
||||
return nil, errors.Join(errs.InvalidArgument, errors.Wrap(err, "can't parse base url"))
|
||||
}
|
||||
var cf Config
|
||||
if len(config) > 0 {
|
||||
@@ -40,7 +40,7 @@ func New(baseURL string, config ...Config) (*Client, error) {
|
||||
cf.Headers = make(map[string]string)
|
||||
}
|
||||
return &Client{
|
||||
baseURL: parsedBaseURL,
|
||||
baseURL: baseURL,
|
||||
Config: cf,
|
||||
}, nil
|
||||
}
|
||||
@@ -60,21 +60,11 @@ type HttpResponse struct {
|
||||
}
|
||||
|
||||
func (r *HttpResponse) UnmarshalBody(out any) error {
|
||||
body, err := r.BodyUncompressed()
|
||||
err := json.Unmarshal(r.Body(), out)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "can't uncompress body from %v", r.URL)
|
||||
}
|
||||
switch strings.ToLower(string(r.Header.ContentType())) {
|
||||
case "application/json", "application/json; charset=utf-8":
|
||||
if err := json.Unmarshal(body, out); err != nil {
|
||||
return errors.Wrapf(err, "can't unmarshal json body from %s, %q", r.URL, string(body))
|
||||
}
|
||||
return nil
|
||||
case "text/plain", "text/plain; charset=utf-8":
|
||||
return errors.Errorf("can't unmarshal plain text %q", string(body))
|
||||
default:
|
||||
return errors.Errorf("unsupported content type: %s, contents: %v", r.Header.ContentType(), string(r.Body()))
|
||||
return errors.Wrapf(err, "can't unmarshal json body from %v, %v", r.URL, string(r.Body()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Client) request(ctx context.Context, reqOptions RequestOptions) (*HttpResponse, error) {
|
||||
@@ -87,14 +77,9 @@ func (h *Client) request(ctx context.Context, reqOptions RequestOptions) (*HttpR
|
||||
for k, v := range reqOptions.Header {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
parsedUrl := h.BaseURL()
|
||||
parsedUrl.Path = path.Join(parsedUrl.Path, reqOptions.path)
|
||||
baseQuery := parsedUrl.Query()
|
||||
for k, v := range reqOptions.Query {
|
||||
baseQuery[k] = v
|
||||
}
|
||||
parsedUrl.RawQuery = baseQuery.Encode()
|
||||
parsedUrl := utils.Must(url.Parse(h.baseURL)) // checked in httpclient.New
|
||||
parsedUrl.Path = reqOptions.path
|
||||
parsedUrl.RawQuery = reqOptions.Query.Encode()
|
||||
|
||||
// remove %20 from url (empty space)
|
||||
url := strings.TrimSuffix(parsedUrl.String(), "%20")
|
||||
@@ -126,7 +111,6 @@ func (h *Client) request(ctx context.Context, reqOptions RequestOptions) (*HttpR
|
||||
logger = logger.With(
|
||||
slog.Int("status_code", resp.StatusCode()),
|
||||
slog.String("resp_content_type", string(resp.Header.ContentType())),
|
||||
slog.String("resp_content_encoding", string(resp.Header.ContentEncoding())),
|
||||
slog.Int("resp_content_length", len(resp.Body())),
|
||||
)
|
||||
}
|
||||
@@ -150,12 +134,6 @@ func (h *Client) request(ctx context.Context, reqOptions RequestOptions) (*HttpR
|
||||
return &httpResponse, nil
|
||||
}
|
||||
|
||||
// BaseURL returns the cloned base URL of the client.
|
||||
func (h *Client) BaseURL() *url.URL {
|
||||
u := *h.baseURL
|
||||
return &u
|
||||
}
|
||||
|
||||
func (h *Client) Do(ctx context.Context, method, path string, reqOptions RequestOptions) (*HttpResponse, error) {
|
||||
reqOptions.path = path
|
||||
reqOptions.method = method
|
||||
|
||||
@@ -119,10 +119,10 @@ type Config struct {
|
||||
// - Text (default)
|
||||
// - JSON
|
||||
// - GCP: Output format for Stackdriver Logging/Cloud Logging or others GCP services.
|
||||
Output string `mapstructure:"output" env:"OUTPUT" envDefault:"text"`
|
||||
Output string `mapstructure:"output"`
|
||||
|
||||
// Debug is enabled logger level debug. (default: false)
|
||||
Debug bool `mapstructure:"debug" env:"DEBUG" envDefault:"false"`
|
||||
Debug bool `mapstructure:"debug"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -53,7 +53,6 @@ func New(config Config) fiber.Handler {
|
||||
slog.Any("x-forwarded-for", c.IPs()),
|
||||
slog.String("user-agent", string(c.Context().UserAgent())),
|
||||
slog.Any("params", c.AllParams()),
|
||||
slog.Any("query", c.Queries()),
|
||||
slog.Int("length", len((c.Body()))),
|
||||
}
|
||||
|
||||
|
||||
@@ -17,3 +17,12 @@ sql:
|
||||
sql_package: "pgx/v5"
|
||||
rename:
|
||||
id: "Id"
|
||||
- schema: "./modules/nodesale/database/postgresql/migrations"
|
||||
queries: "./modules/nodesale/database/postgresql/queries"
|
||||
engine: "postgresql"
|
||||
gen:
|
||||
go:
|
||||
package: "gen"
|
||||
out: "./modules/nodesale/repository/postgres/gen"
|
||||
sql_package: "pgx/v5"
|
||||
emit_interface: true
|
||||
|
||||
Reference in New Issue
Block a user