Skip to content

Commit 0e21135

Browse files
committed
feat: Add V3 billing opt-out and twin admin management to Go client
Implement client methods for V3 billing opt-out functionality including OptOutOfV3Billing, AddTwinAdmin, RemoveTwinAdmin, GetAllowedTwinAdmins, and IsNodeOptedOutOfV3Billing. Add corresponding event types NodeV3BillingOptedOut, TwinAdminAdded, and TwinAdminRemoved. Include comprehensive unit tests and update error definitions for new tfgrid and smart contract module errors.
1 parent 9620380 commit 0e21135

File tree

4 files changed

+195
-0
lines changed

4 files changed

+195
-0
lines changed

clients/tfchain-client-go/events.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,25 @@ type ZosVersionUpdated struct {
331331
Topics []types.Hash
332332
}
333333

334+
type NodeV3BillingOptedOut struct {
335+
Phase types.Phase
336+
NodeID types.U32 `json:"node_id"`
337+
OptedOutAt types.U64 `json:"opted_out_at"`
338+
Topics []types.Hash
339+
}
340+
341+
type TwinAdminAdded struct {
342+
Phase types.Phase
343+
Account AccountID `json:"account_id"`
344+
Topics []types.Hash
345+
}
346+
347+
type TwinAdminRemoved struct {
348+
Phase types.Phase
349+
Account AccountID `json:"account_id"`
350+
Topics []types.Hash
351+
}
352+
334353
type EventSchedulerCallUnavailable struct {
335354
Phase types.Phase
336355
Task types.TaskAddress
@@ -467,6 +486,9 @@ type EventRecords struct {
467486
TfgridModule_FarmingPolicySet []FarmingPolicySet //nolint:stylecheck,golint
468487
TfgridModule_FarmCertificationSet []FarmCertificationSet //nolint:stylecheck,golint
469488
TfgridModule_ZosVersionUpdated []ZosVersionUpdated //nolint:stylecheck,golint
489+
TfgridModule_NodeV3BillingOptedOut []NodeV3BillingOptedOut //nolint:stylecheck,golint
490+
TfgridModule_TwinAdminAdded []TwinAdminAdded //nolint:stylecheck,golint
491+
TfgridModule_TwinAdminRemoved []TwinAdminRemoved //nolint:stylecheck,golint
470492

471493
// burn module events
472494
BurningModule_BurnTransactionCreated []BurnTransactionCreated //nolint:stylecheck,golint

clients/tfchain-client-go/node.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,124 @@ func (s *Substrate) GetDedicatedNodePrice(nodeID uint32) (uint64, error) {
805805
return uint64(price), nil
806806
}
807807

808+
// OptOutOfV3Billing opts a node out of the v3 billing system, opening a free migration window.
809+
// Only the farmer who owns the node can call this.
810+
func (s *Substrate) OptOutOfV3Billing(identity Identity, nodeID uint32) (hash types.Hash, err error) {
811+
cl, meta, err := s.GetClient()
812+
if err != nil {
813+
return hash, err
814+
}
815+
816+
c, err := types.NewCall(meta, "TfgridModule.opt_out_of_v3_billing", nodeID)
817+
if err != nil {
818+
return hash, errors.Wrap(err, "failed to create call")
819+
}
820+
821+
callResponse, err := s.Call(cl, meta, identity, c)
822+
if err != nil {
823+
return hash, errors.Wrap(err, "failed to opt out of v3 billing")
824+
}
825+
826+
return callResponse.Hash, nil
827+
}
828+
829+
// AddTwinAdmin adds an account to the list of twin admins allowed to deploy on opted-out nodes.
830+
// Requires council (restricted) origin.
831+
func (s *Substrate) AddTwinAdmin(identity Identity, account AccountID) (hash types.Hash, err error) {
832+
cl, meta, err := s.GetClient()
833+
if err != nil {
834+
return hash, err
835+
}
836+
837+
c, err := types.NewCall(meta, "TfgridModule.add_twin_admin", account)
838+
if err != nil {
839+
return hash, errors.Wrap(err, "failed to create call")
840+
}
841+
842+
callResponse, err := s.Call(cl, meta, identity, c)
843+
if err != nil {
844+
return hash, errors.Wrap(err, "failed to add twin admin")
845+
}
846+
847+
return callResponse.Hash, nil
848+
}
849+
850+
// RemoveTwinAdmin removes an account from the list of twin admins allowed to deploy on opted-out nodes.
851+
// Requires council (restricted) origin.
852+
func (s *Substrate) RemoveTwinAdmin(identity Identity, account AccountID) (hash types.Hash, err error) {
853+
cl, meta, err := s.GetClient()
854+
if err != nil {
855+
return hash, err
856+
}
857+
858+
c, err := types.NewCall(meta, "TfgridModule.remove_twin_admin", account)
859+
if err != nil {
860+
return hash, errors.Wrap(err, "failed to create call")
861+
}
862+
863+
callResponse, err := s.Call(cl, meta, identity, c)
864+
if err != nil {
865+
return hash, errors.Wrap(err, "failed to remove twin admin")
866+
}
867+
868+
return callResponse.Hash, nil
869+
}
870+
871+
// GetAllowedTwinAdmins returns the list of accounts allowed to deploy on opted-out nodes.
872+
// Returns an empty slice if no admins have been configured.
873+
func (s *Substrate) GetAllowedTwinAdmins() ([]AccountID, error) {
874+
cl, meta, err := s.GetClient()
875+
if err != nil {
876+
return nil, err
877+
}
878+
879+
key, err := types.CreateStorageKey(meta, "TfgridModule", "AllowedTwinAdmins")
880+
if err != nil {
881+
return nil, errors.Wrap(err, "failed to create substrate query key")
882+
}
883+
884+
raw, err := cl.RPC.State.GetStorageRawLatest(key)
885+
if err != nil {
886+
return nil, errors.Wrap(err, "failed to lookup allowed twin admins")
887+
}
888+
889+
if len(*raw) == 0 {
890+
return []AccountID{}, nil
891+
}
892+
893+
var admins []AccountID
894+
if err := Decode(*raw, &admins); err != nil {
895+
return nil, errors.Wrap(err, "failed to decode allowed twin admins")
896+
}
897+
898+
return admins, nil
899+
}
900+
901+
// IsNodeOptedOutOfV3Billing returns true if the node has opted out of v3 billing.
902+
func (s *Substrate) IsNodeOptedOutOfV3Billing(nodeID uint32) (bool, error) {
903+
cl, meta, err := s.GetClient()
904+
if err != nil {
905+
return false, err
906+
}
907+
908+
bytes, err := Encode(nodeID)
909+
if err != nil {
910+
return false, errors.Wrap(err, "substrate: encoding error building query arguments")
911+
}
912+
913+
key, err := types.CreateStorageKey(meta, "TfgridModule", "NodeV3BillingOptOut", bytes)
914+
if err != nil {
915+
return false, errors.Wrap(err, "failed to create substrate query key")
916+
}
917+
918+
raw, err := cl.RPC.State.GetStorageRawLatest(key)
919+
if err != nil {
920+
return false, errors.Wrap(err, "failed to lookup node v3 billing opt-out")
921+
}
922+
923+
return len(*raw) > 0, nil
924+
}
925+
808926
// SetNodeCertificate sets the node certificate type
809927
func (s *Substrate) SetNodeCertificate(identity Identity, id uint32, cert NodeCertification) error {
810928
cl, meta, err := s.GetClient()

clients/tfchain-client-go/node_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,54 @@ func TestSetDedicatedNodePrice(t *testing.T) {
6767
require.Equal(t, uint64(price), priceSet)
6868
}
6969

70+
func TestOptOutOfV3Billing(t *testing.T) {
71+
cl := startLocalConnection(t)
72+
defer cl.Close()
73+
74+
// Bob is the farmer
75+
identity, err := NewIdentityFromSr25519Phrase(BobMnemonics)
76+
require.NoError(t, err)
77+
78+
farmID, twinID := assertCreateFarm(t, cl)
79+
nodeID := assertCreateNode(t, cl, farmID, twinID, identity)
80+
81+
_, err = cl.OptOutOfV3Billing(identity, nodeID)
82+
// NodeV3BillingOptOutAlreadyEnabled is acceptable on re-runs (opt-out is permanent)
83+
if err != nil {
84+
require.EqualError(t, err, "NodeV3BillingOptOutAlreadyEnabled")
85+
}
86+
87+
optedOut, err := cl.IsNodeOptedOutOfV3Billing(nodeID)
88+
require.NoError(t, err)
89+
require.True(t, optedOut)
90+
}
91+
92+
func TestGetAllowedTwinAdmins(t *testing.T) {
93+
cl := startLocalConnection(t)
94+
defer cl.Close()
95+
96+
rootIdentity, err := NewIdentityFromSr25519Phrase(AliceMnemonics)
97+
require.NoError(t, err)
98+
99+
bobAccount, err := FromAddress(BobAddress)
100+
require.NoError(t, err)
101+
102+
// Ensure Bob is in the list
103+
_, err = cl.AddTwinAdmin(rootIdentity, bobAccount)
104+
require.NoError(t, err)
105+
admins, err := cl.GetAllowedTwinAdmins()
106+
require.NoError(t, err)
107+
require.Contains(t, admins, bobAccount)
108+
109+
// Clean up
110+
_, err = cl.RemoveTwinAdmin(rootIdentity, bobAccount)
111+
require.NoError(t, err)
112+
113+
admins, err = cl.GetAllowedTwinAdmins()
114+
require.NoError(t, err)
115+
require.NotContains(t, admins, bobAccount)
116+
}
117+
70118
func TestUptimeReport(t *testing.T) {
71119
cl := startLocalConnection(t)
72120
defer cl.Close()

clients/tfchain-client-go/utils.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ var smartContractModuleErrors = []string{
129129
"UnauthorizedToSetExtraFee",
130130
"RewardDistributionError",
131131
"ContractPaymentStateNotExists",
132+
"OnlyTwinAdminCanDeployOnThisNode",
132133
}
133134

134135
// https://github.com/threefoldtech/tfchain/blob/development/substrate-node/pallets/pallet-tfgrid/src/lib.rs#L442
@@ -249,6 +250,12 @@ var tfgridModuleErrors = []string{
249250
"InvalidRelayAddress",
250251
"InvalidTimestampHint",
251252
"InvalidStorageInput",
253+
"TwinTransferRequestNotFound",
254+
"TwinTransferNewAccountHasTwin",
255+
"TwinTransferPendingExists",
256+
"NodeV3BillingOptOutAlreadyEnabled",
257+
"AlreadyTwinAdmin",
258+
"NotTwinAdmin",
252259
}
253260

254261
// https://github.com/threefoldtech/tfchain/blob/development/substrate-node/pallets/pallet-tft-bridge/src/lib.rs#L152

0 commit comments

Comments
 (0)