feat: prepare unit tests

This commit is contained in:
Waris Aiemworawutikul
2024-06-05 18:23:15 +07:00
parent 2223bcf1d0
commit 73ac0ef6b5
15 changed files with 802 additions and 109 deletions

2
go.mod
View File

@@ -9,6 +9,7 @@ require (
github.com/btcsuite/btcd/btcutil v1.1.5
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
@@ -37,7 +38,6 @@ require (
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.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // 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

View File

@@ -0,0 +1,2 @@
-- name: ClearEvents :exec
DELETE FROM events;

View File

@@ -32,8 +32,8 @@ func (p *Processor) processDelegate(ctx context.Context, qtx gen.Querier, block
if valid {
for _, node := range nodes {
ownerAddress := p.pubkeyToAddress(node.OwnerPublicKey)
if !bytes.Equal(
ownerAddress, err := p.pubkeyToTaprootAddress(node.OwnerPublicKey, event.rawScript)
if err != nil || !bytes.Equal(
[]byte(ownerAddress.EncodeAddress()),
[]byte(event.txAddress.EncodeAddress()),
) {
@@ -53,7 +53,7 @@ func (p *Processor) processDelegate(ctx context.Context, qtx gen.Querier, block
BlockHeight: int32(block.Header.Height),
Valid: valid,
WalletAddress: event.txAddress.EncodeAddress(),
Metadata: []byte{},
Metadata: []byte("{}"),
})
if err != nil {
return fmt.Errorf("Failed to insert event : %w", err)
@@ -63,7 +63,7 @@ func (p *Processor) processDelegate(ctx context.Context, qtx gen.Querier, block
SaleBlock: int32(event.transaction.BlockHeight),
SaleTxIndex: int32(event.transaction.Index),
Delegatee: pgtype.Text{
String: string(delegate.DelegateePublicKey),
String: delegate.DelegateePublicKey,
Valid: true,
},
NodeIds: nodeIds,

View File

@@ -0,0 +1 @@
package nodesale

View File

@@ -15,34 +15,34 @@ import (
func (p *Processor) processDeploy(ctx context.Context, qtx gen.Querier, block *types.Block, event nodesaleEvent) error {
valid := true
deploy := event.eventMessage.Deploy
sellerAddr := p.pubkeyToAddress(string(deploy.SellerPublicKey))
if !bytes.Equal(
sellerAddr, err := p.pubkeyToTaprootAddress(deploy.SellerPublicKey, event.rawScript)
if err != nil || !bytes.Equal(
[]byte(sellerAddr.EncodeAddress()),
[]byte(event.txAddress.EncodeAddress()),
) {
valid = false
}
tiers := make([][]byte, len(deploy.Tiers))
for _, tier := range 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 = append(tiers, tierJson)
tiers[i] = tierJson
}
err := qtx.AddEvent(ctx, gen.AddEventParams{
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: block.Header.Hash.String(),
BlockHeight: int32(block.Header.Height),
BlockHash: event.transaction.BlockHash.String(),
BlockHeight: int32(event.transaction.BlockHeight),
Valid: valid,
WalletAddress: event.txAddress.EncodeAddress(),
Metadata: []byte{},
Metadata: []byte("{}"),
})
if err != nil {
return fmt.Errorf("Failed to insert event : %w", err)
@@ -61,7 +61,7 @@ func (p *Processor) processDeploy(ctx context.Context, qtx gen.Querier, block *t
Valid: true,
},
Tiers: tiers,
SellerPublicKey: string(deploy.SellerPublicKey),
SellerPublicKey: deploy.SellerPublicKey,
MaxPerAddress: int32(deploy.MaxPerAddress),
DeployTxHash: event.transaction.TxHash.String(),
MaxDiscountPercentage: int32(deploy.MaxDiscountPercentage),

View File

@@ -0,0 +1,177 @@
package nodesale
import (
"context"
"encoding/hex"
"os"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"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) {
// call flag.Parse() here if TestMain uses flags
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)
os.Exit(res)
}
func TestDeployInvalid(t *testing.T) {
prvKey, _ := btcec.NewPrivateKey()
message := &protobuf.NodeSaleEvent{
Action: protobuf.Action_ACTION_DEPLOY,
Deploy: &protobuf.ActionDeploy{
Name: "testdeploy",
StartsAt: 100,
EndsAt: 200,
Tiers: []*protobuf.Tier{
{
PriceSat: 100,
Limit: 5,
MaxPerAddress: 100,
},
{
PriceSat: 200,
Limit: 5,
MaxPerAddress: 100,
},
},
SellerPublicKey: "0102030405",
MaxPerAddress: 100,
},
}
event, block := assembleTestEvent(prvKey, "0101010101", "0101010101", 0, 0, message)
p.processDeploy(ctx, qtx, block, event)
}
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)
script, _ := builder.Script()
tapleaf := txscript.NewBaseTapLeaf(script)
scriptTree := txscript.AssembleTaprootScriptTree(tapleaf)
rootHash := scriptTree.RootNode.TapHash()
tapkey := txscript.ComputeTaprootOutputKey(privateKey.PubKey(), rootHash[:])
addressTaproot, _ := btcutil.NewAddressTaproot(schnorr.SerializePubKey(tapkey), p.network.ChainParams())
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,
txAddress: addressTaproot,
rawScript: script,
}
block := &types.Block{
Header: types.BlockHeader{
Timestamp: time.Now().UTC(),
},
}
return event, block
}
func TestDeployValid(t *testing.T) {
privateKey, _ := btcec.NewPrivateKey()
pubkeyHex := hex.EncodeToString(privateKey.PubKey().SerializeCompressed())
message := &protobuf.NodeSaleEvent{
Action: protobuf.Action_ACTION_DEPLOY,
Deploy: &protobuf.ActionDeploy{
Name: "testdeploy",
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,
},
}
event, block := assembleTestEvent(privateKey, "0202020202", "0202020202", 0, 0, message)
p.processDeploy(ctx, qtx, block, event)
}

View File

@@ -23,7 +23,7 @@ import (
)
type Processor struct {
repository repository.Repository
repository *repository.Repository
nodesaleGenesis int32
nodesaleGenesisHash chainhash.Hash
btcClient *datasources.BitcoinNodeDatasource
@@ -73,10 +73,10 @@ func (p *Processor) Name() string {
return "nodesale"
}
func extractNodesaleData(witness [][]byte) ([]byte, bool) {
tokenizer, isTapScript := extractTapScript(witness)
func extractNodesaleData(witness [][]byte) ([]byte, []byte, bool) {
tokenizer, rawScript, isTapScript := extractTapScript(witness)
if !isTapScript {
return []byte{}, false
return []byte{}, []byte{}, false
}
state := 0
for tokenizer.Next() {
@@ -103,12 +103,12 @@ func extractNodesaleData(witness [][]byte) ([]byte, bool) {
case 3:
if tokenizer.Opcode() == txscript.OP_PUSHDATA1 {
data := tokenizer.Data()
return data, true
return data, rawScript, true
}
state = 0
}
}
return []byte{}, false
return []byte{}, []byte{}, false
}
type nodesaleEvent struct {
@@ -117,13 +117,14 @@ type nodesaleEvent struct {
eventJson []byte
txAddress btcutil.Address
rawData []byte
rawScript []byte
}
func (p *Processor) parseTransactions(ctx context.Context, transactions []*types.Transaction) ([]nodesaleEvent, error) {
events := make([]nodesaleEvent, 1)
for _, t := range transactions {
for _, txIn := range t.TxIn {
data, isNodesale := extractNodesaleData(txIn.Witness)
data, rawScript, isNodesale := extractNodesaleData(txIn.Witness)
if !isNodesale {
continue
}
@@ -162,6 +163,7 @@ func (p *Processor) parseTransactions(ctx context.Context, transactions []*types
eventJson: eventJson,
txAddress: addresses[0],
rawData: data,
rawScript: rawScript,
})
}
}

View File

@@ -151,7 +151,7 @@ type ActionDeploy struct {
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 []byte `protobuf:"bytes,5,opt,name=sellerPublicKey,proto3" json:"sellerPublicKey,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"`
}
@@ -216,11 +216,11 @@ func (x *ActionDeploy) GetTiers() []*Tier {
return nil
}
func (x *ActionDeploy) GetSellerPublicKey() []byte {
func (x *ActionDeploy) GetSellerPublicKey() string {
if x != nil {
return x.SellerPublicKey
}
return nil
return ""
}
func (x *ActionDeploy) GetMaxPerAddress() uint32 {
@@ -306,7 +306,7 @@ type ActionPurchase struct {
unknownFields protoimpl.UnknownFields
Payload *PurchasePayload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
SellerSignature []byte `protobuf:"bytes,2,opt,name=sellerSignature,proto3" json:"sellerSignature,omitempty"`
SellerSignature string `protobuf:"bytes,2,opt,name=sellerSignature,proto3" json:"sellerSignature,omitempty"`
}
func (x *ActionPurchase) Reset() {
@@ -348,11 +348,11 @@ func (x *ActionPurchase) GetPayload() *PurchasePayload {
return nil
}
func (x *ActionPurchase) GetSellerSignature() []byte {
func (x *ActionPurchase) GetSellerSignature() string {
if x != nil {
return x.SellerSignature
}
return nil
return ""
}
type PurchasePayload struct {
@@ -361,9 +361,10 @@ type PurchasePayload struct {
unknownFields protoimpl.UnknownFields
DeployID *ActionID `protobuf:"bytes,1,opt,name=deployID,proto3" json:"deployID,omitempty"`
BuyerPublicKey []byte `protobuf:"bytes,2,opt,name=buyerPublicKey,proto3" json:"buyerPublicKey,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() {
@@ -405,11 +406,11 @@ func (x *PurchasePayload) GetDeployID() *ActionID {
return nil
}
func (x *PurchasePayload) GetBuyerPublicKey() []byte {
func (x *PurchasePayload) GetBuyerPublicKey() string {
if x != nil {
return x.BuyerPublicKey
}
return nil
return ""
}
func (x *PurchasePayload) GetNodeIDs() []uint32 {
@@ -426,6 +427,13 @@ func (x *PurchasePayload) GetTotalAmountSat() int64 {
return 0
}
func (x *PurchasePayload) GetTimeOutBlock() uint64 {
if x != nil {
return x.TimeOutBlock
}
return 0
}
type ActionID struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -486,7 +494,7 @@ type ActionDelegate struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
DelegateePublicKey []byte `protobuf:"bytes,1,opt,name=delegateePublicKey,proto3" json:"delegateePublicKey,omitempty"`
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"`
}
@@ -523,11 +531,11 @@ func (*ActionDelegate) Descriptor() ([]byte, []int) {
return file_modules_nodesale_protobuf_nodesale_proto_rawDescGZIP(), []int{6}
}
func (x *ActionDelegate) GetDelegateePublicKey() []byte {
func (x *ActionDelegate) GetDelegateePublicKey() string {
if x != nil {
return x.DelegateePublicKey
}
return nil
return ""
}
func (x *ActionDelegate) GetNodeIDs() []uint32 {
@@ -576,7 +584,7 @@ var file_modules_nodesale_protobuf_nodesale_proto_rawDesc = []byte{
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, 0x0c, 0x52, 0x0f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72,
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,
@@ -595,41 +603,43 @@ var file_modules_nodesale_protobuf_nodesale_proto_rawDesc = []byte{
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, 0x0c, 0x52, 0x0f, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x69, 0x67,
0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xab, 0x01, 0x0a, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68,
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, 0x0c, 0x52, 0x0e, 0x62, 0x75, 0x79, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
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, 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, 0x0c, 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,
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 (

View File

@@ -23,9 +23,9 @@ message ActionDeploy {
uint32 startsAt = 2;
uint32 endsAt = 3;
repeated Tier tiers = 4;
bytes sellerPublicKey = 5;
string sellerPublicKey = 5;
uint32 maxPerAddress = 6;
uint32 maxDiscountPercentage = 7;
uint32 maxDiscountPercentage = 7;
}
message Tier {
@@ -36,14 +36,15 @@ message Tier {
message ActionPurchase {
PurchasePayload payload = 1;
bytes sellerSignature = 2;
string sellerSignature = 2;
}
message PurchasePayload {
ActionID deployID = 1;
bytes buyerPublicKey = 2;
string buyerPublicKey = 2;
repeated uint32 nodeIDs = 3;
int64 totalAmountSat = 4;
uint64 timeOutBlock = 5;
}
message ActionID {
@@ -52,7 +53,7 @@ message ActionID {
}
message ActionDelegate {
bytes delegateePublicKey = 1;
string delegateePublicKey = 1;
repeated uint32 nodeIDs = 2;
ActionID deployID = 3;
ActionID deployID = 3;
}

View File

@@ -2,15 +2,42 @@ package nodesale
import (
"encoding/hex"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
)
func (p *Processor) pubkeyToAddress(pubkey string) btcutil.Address {
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 string) btcutil.Address {
pubKeyBytes, _ := hex.DecodeString(pubkey)
pubKey, _ := btcec.ParsePubKey(pubKeyBytes)
sellerAddr, _ := btcutil.NewAddressTaproot(schnorr.SerializePubKey(pubKey), p.network.ChainParams())
return sellerAddr
addrPubKey, _ := btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), p.network.ChainParams())
addrPubKeyHash := addrPubKey.AddressPubKeyHash()
return addrPubKeyHash
}

View File

@@ -5,6 +5,7 @@ import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"slices"
@@ -19,13 +20,19 @@ import (
"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
buyerAddr := p.pubkeyToAddress(string(payload.BuyerPublicKey))
if !bytes.Equal(
buyerAddr, err := p.pubkeyToTaprootAddress(payload.BuyerPublicKey, event.rawScript)
if err != nil || !bytes.Equal(
[]byte(buyerAddr.EncodeAddress()),
[]byte(event.txAddress.EncodeAddress()),
) {
@@ -57,19 +64,29 @@ func (p *Processor) processPurchase(ctx context.Context, qtx gen.Querier, block
}
}
if valid {
if payload.TimeOutBlock < uint64(event.transaction.BlockHeight) {
valid = false
}
}
if valid {
// verified signature
payloadBytes, _ := proto.Marshal(payload)
signatureBytes, _ := hex.DecodeString(string(purchase.SellerSignature))
signature, _ := ecdsa.ParseSignature(signatureBytes)
hash := sha256.Sum256(payloadBytes)
pubkeyBytes, _ := hex.DecodeString(deploy.SellerPublicKey)
pubKey, _ := btcec.ParsePubKey(pubkeyBytes)
verified := signature.Verify(hash[:], pubKey)
if !verified {
signatureBytes, _ := hex.DecodeString(purchase.SellerSignature)
signature, err := ecdsa.ParseSignature(signatureBytes)
if err != nil {
valid = false
}
if valid {
hash := sha256.Sum256(payloadBytes)
pubkeyBytes, _ := hex.DecodeString(deploy.SellerPublicKey)
pubKey, _ := btcec.ParsePubKey(pubkeyBytes)
verified := signature.Verify(hash[:], pubKey)
if !verified {
valid = false
}
}
}
var tiers []protobuf.Tier
@@ -123,12 +140,12 @@ func (p *Processor) processPurchase(ctx context.Context, qtx gen.Querier, block
}
var txPaid int64 = 0
meta := metaData{}
if valid {
// get total amount paid to seller
sellerAddr := p.pubkeyToAddress(deploy.SellerPublicKey)
sellerAddr := p.pubkeyToPkHashAddress(deploy.SellerPublicKey)
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()),
@@ -136,13 +153,12 @@ func (p *Processor) processPurchase(ctx context.Context, qtx gen.Querier, block
txPaid += txOut.Value
}
}
meta.PaidTotalAmount = txPaid
meta.ReportedTotalAmount = payload.TotalAmountSat
// total amount paid is greater than report paid
if txPaid < payload.TotalAmountSat {
valid = false
}
}
if valid {
// calculate total price
var totalPrice int64 = 0
for i := 0; i < len(tiers); i++ {
@@ -155,6 +171,7 @@ func (p *Processor) processPurchase(ctx context.Context, qtx gen.Querier, block
if decimal%100 >= 50 {
maxDiscounted++
}
meta.ExpectedTotalAmountDiscounted = maxDiscounted
if payload.TotalAmountSat < maxDiscounted {
valid = false
}
@@ -168,12 +185,12 @@ func (p *Processor) processPurchase(ctx context.Context, qtx gen.Querier, block
buyerOwnedNodes, err = qtx.GetNodesByOwner(ctx, gen.GetNodesByOwnerParams{
SaleBlock: deploy.BlockHeight,
SaleTxIndex: deploy.TxIndex,
OwnerPublicKey: string(payload.BuyerPublicKey),
OwnerPublicKey: payload.BuyerPublicKey,
})
if err != nil {
return fmt.Errorf("Failed to GetNodesByOwner : %w", err)
}
if len(buyerOwnedNodes) > int(deploy.MaxPerAddress) {
if len(buyerOwnedNodes)+len(payload.NodeIDs) > int(deploy.MaxPerAddress) {
valid = false
}
}
@@ -183,40 +200,31 @@ func (p *Processor) processPurchase(ctx context.Context, qtx gen.Querier, block
// count each tiers
// check limited for each tier
ownedTiersCount := make([]uint32, len(tiers))
currentTier := -1
var tierSum uint32 = 0
for _, node := range buyerOwnedNodes {
for uint32(node.TierIndex) >= tierSum && currentTier < len(tiers)-1 {
currentTier++
tierSum += tiers[currentTier].Limit
}
if uint32(node.TierIndex) < tierSum {
ownedTiersCount[currentTier]++
} else {
valid = false
}
ownedTiersCount[node.TierIndex]++
}
for i := 0; i < len(tiers); i++ {
ownedTiersCount[i] += buyingTiersCount[i]
if ownedTiersCount[i] > tiers[i].Limit {
if ownedTiersCount[i]+buyingTiersCount[i] > tiers[i].MaxPerAddress {
valid = false
break
}
}
}
err := qtx.AddEvent(ctx, gen.AddEventParams{
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: block.Header.Hash.String(),
BlockHeight: int32(block.Header.Height),
BlockHash: event.transaction.BlockHash.String(),
BlockHeight: int32(event.transaction.BlockHeight),
Valid: valid,
WalletAddress: event.txAddress.EncodeAddress(),
Metadata: []byte{},
Metadata: metaDataBytes,
})
if err != nil {
return fmt.Errorf("Failed to insert event : %w", err)
@@ -226,12 +234,12 @@ func (p *Processor) processPurchase(ctx context.Context, qtx gen.Querier, block
// add to node
for _, nodeId := range payload.NodeIDs {
err := qtx.AddNode(ctx, gen.AddNodeParams{
SaleBlock: int32(event.transaction.BlockHeight),
SaleTxIndex: int32(event.transaction.Index),
SaleBlock: deploy.BlockHeight,
SaleTxIndex: deploy.TxIndex,
NodeID: int32(nodeId),
TierIndex: nodeIdToTier[nodeId],
DelegatedTo: pgtype.Text{Valid: false},
OwnerPublicKey: string(payload.BuyerPublicKey),
OwnerPublicKey: payload.BuyerPublicKey,
PurchaseTxHash: event.transaction.TxHash.String(),
DelegateTxHash: pgtype.Text{Valid: false},
})

View File

@@ -0,0 +1,445 @@
package nodesale
import (
"crypto/sha256"
"encoding/hex"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"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"
"google.golang.org/protobuf/proto"
)
func TestInvalidPurchase(t *testing.T) {
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,
},
BuyerPublicKey: buyerPubkeyHex,
},
},
}
event, block := assembleTestEvent(buyerPrivateKey, "030303030303", "030303030303", 0, 0, message)
p.processPurchase(ctx, qtx, block, event)
}
func TestInvalidTimestamp(t *testing.T) {
sellerPrivateKey, _ := btcec.NewPrivateKey()
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
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: "deploy_for_puchase",
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,
},
}
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,
},
BuyerPublicKey: buyerPubkeyHex,
},
},
}
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)
}
func TestInvalidBuyerKey(t *testing.T) {
sellerPrivateKey, _ := btcec.NewPrivateKey()
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
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: "deploy_for_puchase",
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,
},
}
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,
},
BuyerPublicKey: sellerPubkeyHex,
},
},
}
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)
}
func TestTimeOut(t *testing.T) {
sellerPrivateKey, _ := btcec.NewPrivateKey()
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
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: "deploy_for_puchase",
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,
},
}
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,
},
BuyerPublicKey: buyerPubkeyHex,
TimeOutBlock: uint64(testBlockHeigh) - 5,
},
},
}
event, block = assembleTestEvent(buyerPrivateKey, "090909090909", "090909090909", 0, 0, message)
p.processPurchase(ctx, qtx, block, event)
}
func TestSignatureInvalid(t *testing.T) {
sellerPrivateKey, _ := btcec.NewPrivateKey()
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
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: "deploy_for_puchase",
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,
},
}
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,
},
BuyerPublicKey: buyerPubkeyHex,
TimeOutBlock: uint64(testBlockHeigh) + 5,
}
payloadBytes, _ := proto.Marshal(payload)
payloadHash := sha256.Sum256(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)
}
func TestValidPurchase(t *testing.T) {
sellerPrivateKey, _ := btcec.NewPrivateKey()
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
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: "deploy_for_puchase",
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,
},
}
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 := sha256.Sum256(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)
}
func TestBuyingLimit(t *testing.T) {
sellerPrivateKey, _ := btcec.NewPrivateKey()
sellerPubkeyHex := hex.EncodeToString(sellerPrivateKey.PubKey().SerializeCompressed())
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: "deploy_for_puchase",
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,
},
}
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 := sha256.Sum256(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 = sha256.Sum256(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)
}

View File

@@ -14,6 +14,7 @@ type Querier interface {
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)
GetLastProcessedBlock(ctx context.Context) (Block, error)
GetNodes(ctx context.Context, arg GetNodesParams) ([]Node, error)

View File

@@ -0,0 +1,19 @@
// 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
`
func (q *Queries) ClearEvents(ctx context.Context) error {
_, err := q.db.Exec(ctx, clearEvents)
return err
}

View File

@@ -2,14 +2,14 @@ package nodesale
import "github.com/btcsuite/btcd/txscript"
func extractTapScript(witness [][]byte) (txscript.ScriptTokenizer, bool) {
func extractTapScript(witness [][]byte) (txscript.ScriptTokenizer, []byte, bool) {
witness = removeAnnexFromWitness(witness)
if len(witness) < 2 {
return txscript.ScriptTokenizer{}, false
return txscript.ScriptTokenizer{}, nil, false
}
script := witness[len(witness)-2]
return txscript.MakeScriptTokenizer(0, script), true
return txscript.MakeScriptTokenizer(0, script), script, true
}
func removeAnnexFromWitness(witness [][]byte) [][]byte {