diff --git a/app/config/mocks/mocks.go b/app/config/mocks/mocks.go index 17549dd..e1a865e 100644 --- a/app/config/mocks/mocks.go +++ b/app/config/mocks/mocks.go @@ -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 { @@ -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 } @@ -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 +} diff --git a/app/config/node.go b/app/config/node.go index 324c129..c10bf3e 100644 --- a/app/config/node.go +++ b/app/config/node.go @@ -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" ) @@ -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 @@ -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) +} diff --git a/app/models/alert_message.go b/app/models/alert_message.go index 2456616..48a1fa9 100644 --- a/app/models/alert_message.go +++ b/app/models/alert_message.go @@ -201,7 +201,7 @@ func (m *AlertMessage) ProcessAlertMessage() AlertMessageInterface { AlertMessage: *m, } case AlertTypeConfiscateUtxo: - return &AlertMessageConfiscateUtxo{ + return &AlertMessageConfiscateTransaction{ AlertMessage: *m, } case AlertTypeBanPeer: diff --git a/app/models/alert_message_confiscate_utxo.go b/app/models/alert_message_confiscate_utxo.go index 88f8afa..5c69104 100644 --- a/app/models/alert_message_confiscate_utxo.go +++ b/app/models/alert_message_confiscate_utxo.go @@ -1,19 +1,58 @@ 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 +} + +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:] + } 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 } diff --git a/app/models/alert_message_freeze_utxo.go b/app/models/alert_message_freeze_utxo.go index 2b30727..c960851 100644 --- a/app/models/alert_message_freeze_utxo.go +++ b/app/models/alert_message_freeze_utxo.go @@ -1,19 +1,75 @@ 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 +} + +type Fund struct { + TransactionOutId [32]byte + Vout [8]byte + EnforceAtHeightStart [8]byte + EnforceAtHeightEnd [8]byte + PolicyExpiresWithConsensus bool } // 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 58 bytes") + } + if len(raw)%57 != 0 { + return fmt.Errorf("freeze alert is not a multiple of 58 bytes") + } + fundCount := len(raw) / 57 + funds := []models.Fund{} + for i := 0; i < fundCount; i++ { + fund := Fund{ + TransactionOutId: [32]byte(raw[0:32]), + Vout: [8]byte(raw[32:40]), + EnforceAtHeightStart: [8]byte(raw[40:48]), + EnforceAtHeightEnd: [8]byte(raw[48:56]), + } + enforceByte := binary.LittleEndian.Uint16(raw[56:57]) + + if enforceByte != uint16(0) { + fund.PolicyExpiresWithConsensus = true + } + funds = append(funds, models.Fund{ + TxOut: models.TxOut{ + TxId: hex.EncodeToString(fund.TransactionOutId[:]), + Vout: int(binary.LittleEndian.Uint64(fund.Vout[:])), + }, + EnforceAtHeight: []models.Enforce{ + { + Start: int(binary.LittleEndian.Uint64(fund.EnforceAtHeightStart[:])), + Stop: int(binary.LittleEndian.Uint64(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 } diff --git a/app/models/alert_message_unfreeze_utxo.go b/app/models/alert_message_unfreeze_utxo.go index ad7c4b8..a2172aa 100644 --- a/app/models/alert_message_unfreeze_utxo.go +++ b/app/models/alert_message_unfreeze_utxo.go @@ -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 diff --git a/app/p2p/server.go b/app/p2p/server.go index 994586c..06c8c04 100644 --- a/app/p2p/server.go +++ b/app/p2p/server.go @@ -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()) diff --git a/go.mod b/go.mod index e0ba050..f30005d 100644 --- a/go.mod +++ b/go.mod @@ -194,7 +194,7 @@ require ( replace github.com/libsv/go-bt/v2 => github.com/ordishs/go-bt/v2 v2.2.5 // Use this specific version of go-bn (galt-tr vs libsv) -replace github.com/libsv/go-bn => github.com/galt-tr/go-bn v0.0.2 +replace github.com/libsv/go-bn => github.com/galt-tr/go-bn v0.0.3 // Stuck on this version because of the Tokenized package replace github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.20.1-beta diff --git a/go.sum b/go.sum index cbed97e..17a0b2b 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/galt-tr/go-bn v0.0.2 h1:tA0guT4GrDuWCAAQp2QZRqf1I0nu4cr8nQJ/sk7x03s= -github.com/galt-tr/go-bn v0.0.2/go.mod h1:U8pPMrGIG/Q0vslgvDrU9fVWskX2kX1+lcmhffF+om4= +github.com/galt-tr/go-bn v0.0.3 h1:Aqb4KlNGca8Gm8ZrSqwANcAThi4sTc5ovfbcx93yTtg= +github.com/galt-tr/go-bn v0.0.3/go.mod h1:U8pPMrGIG/Q0vslgvDrU9fVWskX2kX1+lcmhffF+om4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= diff --git a/hack/publish.go b/hack/publish.go index fac1d51..8c810e7 100644 --- a/hack/publish.go +++ b/hack/publish.go @@ -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" @@ -59,7 +63,7 @@ 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")) case models.AlertTypeUnfreezeUtxo: @@ -137,6 +141,30 @@ func InfoAlert(seq uint, opts ...model.Options) *models.AlertMessage { 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 {