Skip to content

Commit

Permalink
Add support for freezing and confiscation (#12)
Browse files Browse the repository at this point in the history
* Support for confiscation

* Freezing is working

* Fixes for linter

* Fix go mod
  • Loading branch information
galt-tr authored Jan 16, 2024
1 parent 40d7eca commit 891f510
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 24 deletions.
31 changes: 26 additions & 5 deletions app/config/mocks/mocks.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Package mocks is a generated mocking package for the mocks
package mocks

import "context"
import (
"context"

"github.com/libsv/go-bn/models"
)

// Node is a mock type for the SVNode interface
type Node struct {
Expand All @@ -11,10 +15,11 @@ type Node struct {
RPCUser string

// Functions
BanPeerFunc func(ctx context.Context, peer string) error
InvalidateBlockFunc func(ctx context.Context, hash string) error
UnbanPeerFunc func(ctx context.Context, peer string) error

BanPeerFunc func(ctx context.Context, peer string) error
InvalidateBlockFunc func(ctx context.Context, hash string) error
UnbanPeerFunc func(ctx context.Context, peer string) error
AddToConsensusBlacklistFunc func(ctx context.Context, funds []models.Fund) (*models.AddToConsensusBlacklistResponse, error)
AddToConfiscationTransactionWhitelistFunc func(ctx context.Context, tx []models.ConfiscationTransactionDetails) (*models.AddToConfiscationTransactionWhitelistResponse, error)
// Add additional fields if needed to track calls or results
}

Expand Down Expand Up @@ -57,3 +62,19 @@ func (n *Node) UnbanPeer(ctx context.Context, peer string) error {
}
return nil
}

// AddToConsensusBlacklist will call the AddToConsensusBlacklistFunc if not nil, otherwise return nil
func (n *Node) AddToConsensusBlacklist(ctx context.Context, funds []models.Fund) (*models.AddToConsensusBlacklistResponse, error) {
if n.AddToConsensusBlacklistFunc != nil {
return n.AddToConsensusBlacklistFunc(ctx, funds)
}
return nil, nil
}

// AddToConfiscationTransactionWhitelist will call the AddToConfiscationTransactionWhitelistFunc if not nil, otherwise return nil
func (n *Node) AddToConfiscationTransactionWhitelist(ctx context.Context, tx []models.ConfiscationTransactionDetails) (*models.AddToConfiscationTransactionWhitelistResponse, error) {
if n.AddToConfiscationTransactionWhitelistFunc != nil {
return n.AddToConfiscationTransactionWhitelistFunc(ctx, tx)
}
return nil, nil
}
16 changes: 16 additions & 0 deletions app/config/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package config
import (
"context"

"github.com/libsv/go-bn/models"

"github.com/bitcoin-sv/alert-system/app/config/mocks"
"github.com/libsv/go-bn"
)
Expand All @@ -15,6 +17,8 @@ type NodeInterface interface {
GetRPCUser() string
InvalidateBlock(ctx context.Context, hash string) error
UnbanPeer(ctx context.Context, peer string) error
AddToConsensusBlacklist(ctx context.Context, funds []models.Fund) (*models.AddToConsensusBlacklistResponse, error)
AddToConfiscationTransactionWhitelist(ctx context.Context, tx []models.ConfiscationTransactionDetails) (*models.AddToConfiscationTransactionWhitelistResponse, error)
}

// NewNodeConfig creates a new NodeConfig struct
Expand Down Expand Up @@ -67,3 +71,15 @@ func (n *Node) UnbanPeer(ctx context.Context, peer string) error {
c := bn.NewNodeClient(bn.WithCreds(n.RPCUser, n.RPCPassword), bn.WithHost(n.RPCHost))
return c.SetBan(ctx, peer, bn.BanActionRemove, nil)
}

// AddToConsensusBlacklist adds frozen utxos to blacklist
func (n *Node) AddToConsensusBlacklist(ctx context.Context, funds []models.Fund) (*models.AddToConsensusBlacklistResponse, error) {
c := bn.NewNodeClient(bn.WithCreds(n.RPCUser, n.RPCPassword), bn.WithHost(n.RPCHost))
return c.AddToConsensusBlacklist(ctx, funds)
}

// AddToConfiscationTransactionWhitelist adds confiscation transactions to the whitelist
func (n *Node) AddToConfiscationTransactionWhitelist(ctx context.Context, tx []models.ConfiscationTransactionDetails) (*models.AddToConfiscationTransactionWhitelistResponse, error) {
c := bn.NewNodeClient(bn.WithCreds(n.RPCUser, n.RPCPassword), bn.WithHost(n.RPCHost))
return c.AddToConfiscationTransactionWhitelist(ctx, tx)
}
3 changes: 2 additions & 1 deletion app/models/alert_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type AlertMessage struct {
Hash string `json:"hash" toml:"hash" yaml:"hash" bson:"hash" gorm:"<-;type:char(64);index;comment:This is the hash"`
SequenceNumber uint32 `json:"sequence_number" toml:"sequence_number" yaml:"sequence_number" bson:"sequence_number" gorm:"<-;type:int8;index;comment:This is the alert sequence number"`
Raw string `json:"raw" toml:"raw" yaml:"raw" bson:"raw" gorm:"<-;type:text;comment:This is the raw alert message"`
Processed bool `json:"processed" toml:"processed" yaml:"processed" bson:"processed"`

// Private fields (never to be exported)
alertType AlertType
Expand Down Expand Up @@ -201,7 +202,7 @@ func (m *AlertMessage) ProcessAlertMessage() AlertMessageInterface {
AlertMessage: *m,
}
case AlertTypeConfiscateUtxo:
return &AlertMessageConfiscateUtxo{
return &AlertMessageConfiscateTransaction{
AlertMessage: *m,
}
case AlertTypeBanPeer:
Expand Down
53 changes: 47 additions & 6 deletions app/models/alert_message_confiscate_utxo.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,60 @@
package models

import "context"
import (
"context"
"encoding/binary"
"encoding/hex"
"fmt"

// AlertMessageConfiscateUtxo is a confiscate utxo alert
type AlertMessageConfiscateUtxo struct {
"github.com/libsv/go-bn/models"
)

// AlertMessageConfiscateTransaction is a confiscate utxo alert
type AlertMessageConfiscateTransaction struct {
AlertMessage
// TODO finish building out this alert type
Transactions []models.ConfiscationTransactionDetails
}

// ConfiscateTransaction defines the parameters for the confiscation transaction
type ConfiscateTransaction struct {
EnforceAtHeight [8]byte
ID [32]byte
}

// Read reads the alert
func (a *AlertMessageConfiscateUtxo) Read(_ []byte) error {
func (a *AlertMessageConfiscateTransaction) Read(raw []byte) error {
a.Config().Services.Log.Infof("%x", raw)
if len(raw) < 40 {
return fmt.Errorf("confiscation alert is less than 41 bytes")
}
if len(raw)%40 != 0 {
return fmt.Errorf("confiscation alert is not a multiple of 41 bytes")
}
txCount := len(raw) / 40
details := []models.ConfiscationTransactionDetails{}
for i := 0; i < txCount; i++ {
tx := ConfiscateTransaction{
EnforceAtHeight: [8]byte(raw[:8]),
ID: [32]byte(raw[8:40]),
}
detail := models.ConfiscationTransactionDetails{
ConfiscationTransaction: models.ConfiscationTransaction{
EnforceAtHeight: int64(binary.LittleEndian.Uint64(tx.EnforceAtHeight[:])),
Hex: hex.EncodeToString(tx.ID[:]),
},
}
details = append(details, detail)
raw = raw[40:]
}
a.Transactions = details
return nil
}

// Do executes the alert
func (a *AlertMessageConfiscateUtxo) Do(_ context.Context) error {
func (a *AlertMessageConfiscateTransaction) Do(ctx context.Context) error {
_, err := a.Config().Services.Node.AddToConfiscationTransactionWhitelist(ctx, a.Transactions)
if err != nil {
return err
}
return nil
}
80 changes: 76 additions & 4 deletions app/models/alert_message_freeze_utxo.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,91 @@
package models

import "context"
import (
"context"
"encoding/binary"
"encoding/hex"
"fmt"

"github.com/libsv/go-bn/models"
)

// AlertMessageFreezeUtxo is the message for freezing UTXOs
type AlertMessageFreezeUtxo struct {
AlertMessage
// TODO finish building out this alert type
Funds []models.Fund
}

// Fund is the struct defining funds to freeze
type Fund struct {
TransactionOutID [32]byte
Vout uint64
EnforceAtHeightStart uint64
EnforceAtHeightEnd uint64
PolicyExpiresWithConsensus bool
}

// Serialize creates the raw hex string of the fund
func (f *Fund) Serialize() []byte {
raw := []byte{}
raw = append(raw, f.TransactionOutID[:]...)
raw = binary.LittleEndian.AppendUint64(raw, f.Vout)
raw = binary.LittleEndian.AppendUint64(raw, f.EnforceAtHeightStart)
raw = binary.LittleEndian.AppendUint64(raw, f.EnforceAtHeightEnd)
expire := uint8(0)
if f.PolicyExpiresWithConsensus {
expire = uint8(1)
}
raw = append(raw, expire)
return raw
}

// Read reads the message
func (a *AlertMessageFreezeUtxo) Read(_ []byte) error {
func (a *AlertMessageFreezeUtxo) Read(raw []byte) error {
if len(raw) < 57 {
return fmt.Errorf("freeze alert is less than 57 bytes, got %d bytes", len(raw))
}
if len(raw)%57 != 0 {
return fmt.Errorf("freeze alert is not a multiple of 57 bytes, got %d bytes", len(raw))
}
fundCount := len(raw) / 57
funds := []models.Fund{}
for i := 0; i < fundCount; i++ {
fund := Fund{
TransactionOutID: [32]byte(raw[0:32]),
Vout: binary.LittleEndian.Uint64(raw[32:40]),
EnforceAtHeightStart: binary.LittleEndian.Uint64(raw[40:48]),
EnforceAtHeightEnd: binary.LittleEndian.Uint64(raw[48:56]),
}
enforceByte := raw[56]

if enforceByte != uint8(0) {
fund.PolicyExpiresWithConsensus = true
}
funds = append(funds, models.Fund{
TxOut: models.TxOut{
TxId: hex.EncodeToString(fund.TransactionOutID[:]),
Vout: int(fund.Vout),
},
EnforceAtHeight: []models.Enforce{
{
Start: int(fund.EnforceAtHeightStart),
Stop: int(fund.EnforceAtHeightEnd),
},
},
PolicyExpiresWithConsensus: fund.PolicyExpiresWithConsensus,
})
raw = raw[57:]
}
a.Funds = funds

return nil
}

// Do performs the message
func (a *AlertMessageFreezeUtxo) Do(_ context.Context) error {
func (a *AlertMessageFreezeUtxo) Do(ctx context.Context) error {
_, err := a.Config().Services.Node.AddToConsensusBlacklist(ctx, a.Funds)
if err != nil {
return err
}
return nil
}
1 change: 1 addition & 0 deletions app/models/alert_message_unfreeze_utxo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "context"
type AlertMessageUnfreezeUtxo struct {
AlertMessage
// TODO finish building out this alert type
Funds []Fund
}

// Read reads the message from the byte slice
Expand Down
4 changes: 2 additions & 2 deletions app/p2p/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,13 @@ func (s *Server) Subscribe(ctx context.Context, subscriber *pubsub.Subscription,

// Perform alert action
if err = am.Do(ctx); err != nil {
s.config.Services.Log.Infof("failed to do alert action: %s", err.Error())
s.config.Services.Log.Errorf("failed to do alert action: %s", err.Error())
continue
}

// Save the alert message
if err = ak.Save(ctx); err != nil {
s.config.Services.Log.Infof("failed to save alert message: %s", err.Error())
s.config.Services.Log.Errorf("failed to save alert message: %s", err.Error())
}

s.config.Services.Log.Infof("[%s] got alert type: %d, from: %s", subscriber.Topic(), ak.GetAlertType(), msg.ReceivedFrom.String())
Expand Down
2 changes: 1 addition & 1 deletion go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 51 additions & 3 deletions hack/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package main

import (
"context"
"encoding/binary"
"encoding/hex"
"flag"
"fmt"
"strings"
"time"

models2 "github.com/libsv/go-bn/models"

"github.com/bitcoin-sv/alert-system/app/models/model"

"github.com/bitcoin-sv/alert-system/app/config"
Expand Down Expand Up @@ -35,7 +39,7 @@ func main() {
// Load the configuration and services
_appConfig, err := config.LoadDependencies(context.Background(), models.BaseModels, false)
if err != nil {
_appConfig.Services.Log.Fatalf("error loading configuration: %s", err.Error())
log.Fatalf("error loading configuration: %s", err.Error())
}
defer func() {
_appConfig.CloseAll(context.Background())
Expand All @@ -59,9 +63,9 @@ func main() {
case models.AlertTypeUnbanPeer:
//a = UnbanPeerAlert(*sequenceNumber, *peer)
case models.AlertTypeConfiscateUtxo:
panic(fmt.Errorf("not implemented"))
a = confiscateAlert(*sequenceNumber, model.WithAllDependencies(_appConfig))
case models.AlertTypeFreezeUtxo:
panic(fmt.Errorf("not implemented"))
a = freezeAlert(*sequenceNumber, model.WithAllDependencies(_appConfig))
case models.AlertTypeUnfreezeUtxo:
panic(fmt.Errorf("not implemented"))
case models.AlertTypeSetKeys:
Expand Down Expand Up @@ -137,6 +141,50 @@ func InfoAlert(seq uint, opts ...model.Options) *models.AlertMessage {
return newAlert
}

func freezeAlert(seq uint, opts ...model.Options) *models.AlertMessage {
tx, _ := hex.DecodeString("d83dee7aec89a9437345d9676bc727a2592e5b3988f4343931181f86b666eace")
fund := models.Fund{
TransactionOutID: [32]byte(tx),
Vout: uint64(0),
EnforceAtHeightStart: uint64(10000),
EnforceAtHeightEnd: uint64(10100),
PolicyExpiresWithConsensus: false,
}
opts = append(opts, model.New())
newAlert := models.NewAlertMessage(opts...)
newAlert.SetAlertType(models.AlertTypeFreezeUtxo)
newAlert.SetRawMessage(fund.Serialize())
newAlert.SequenceNumber = uint32(seq)
newAlert.SetTimestamp(uint64(time.Now().Second()))
newAlert.SetVersion(0x01)
newAlert.SerializeData()
return newAlert
}

func confiscateAlert(seq uint, opts ...model.Options) *models.AlertMessage {
tx := models2.ConfiscationTransactionDetails{
ConfiscationTransaction: models2.ConfiscationTransaction{
Hex: "dd1b08331cf22da4d27bd1b29019a04a168805d49b48d65a7fec381eb4307d61",
EnforceAtHeight: 10000,
},
}
raw := []byte{}
enforce := [8]byte{}
binary.LittleEndian.PutUint64(enforce[:], uint64(tx.ConfiscationTransaction.EnforceAtHeight))
raw = append(raw, enforce[:]...)
by, _ := hex.DecodeString(tx.ConfiscationTransaction.Hex)
raw = append(raw, by...)
opts = append(opts, model.New())
newAlert := models.NewAlertMessage(opts...)
newAlert.SetAlertType(models.AlertTypeConfiscateUtxo)
newAlert.SetRawMessage(raw)
newAlert.SequenceNumber = uint32(seq)
newAlert.SetTimestamp(uint64(time.Now().Second()))
newAlert.SetVersion(0x01)
newAlert.SerializeData()
return newAlert
}

/*
// BanPeerAlert creates a ban peer alert
func BanPeerAlert(seq uint, peer string) alert.Alert {
Expand Down

0 comments on commit 891f510

Please sign in to comment.