From 35401d52bd92a31bd234432504bcce14e648d76f Mon Sep 17 00:00:00 2001 From: evlekht Date: Fri, 30 Dec 2022 17:54:47 +0400 Subject: [PATCH] Add registerNodeTx builder and service methods, tx now checks/updates AddressStateRegisteredNodeBit --- vms/platformvm/camino_service.go | 77 ++++++++++ vms/platformvm/metrics/camino_tx_metrics.go | 3 + vms/platformvm/msig/msig.go | 27 ++++ vms/platformvm/state/camino.go | 8 ++ vms/platformvm/state/camino_diff.go | 4 + vms/platformvm/state/mock_chain.go | 1 - vms/platformvm/txs/builder/camino_builder.go | 100 +++++++++++-- vms/platformvm/txs/camino_address_state_tx.go | 4 +- vms/platformvm/txs/camino_register_node_tx.go | 9 +- .../txs/executor/camino_tx_executor.go | 131 +++++++++--------- .../txs/executor/camino_tx_executor_test.go | 4 +- 11 files changed, 287 insertions(+), 81 deletions(-) create mode 100644 vms/platformvm/msig/msig.go diff --git a/vms/platformvm/camino_service.go b/vms/platformvm/camino_service.go index abb9b7496824..2c6671ac8e21 100644 --- a/vms/platformvm/camino_service.go +++ b/vms/platformvm/camino_service.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto" "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/keystore" "github.com/ava-labs/avalanchego/vms/platformvm/locked" @@ -348,6 +349,82 @@ func (s *CaminoService) Spend(_ *http.Request, args *SpendArgs, response *SpendR return nil } +type RegisterNodeArgs struct { + api.JSONSpendHeader + + OldNodeID ids.NodeID `json:"oldNodeID"` + NewNodeID ids.NodeID `json:"newNodeID"` + ConsortiumMemberAddress string `json:"consortiumMemberAddress"` +} + +// RegisterNode issues an RegisterNodeTx +func (s *CaminoService) RegisterNode(_ *http.Request, args *RegisterNodeArgs, reply *api.JSONTxIDChangeAddr) error { + s.vm.ctx.Log.Debug("Platform: RegisterNode called") + + // Parse the from addresses + fromAddrs, err := avax.ParseServiceAddresses(s.addrManager, args.From) + if err != nil { + return err + } + + user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password) + if err != nil { + return err + } + + // Get the user's keys + privKeys, err := keystore.GetKeychain(user, fromAddrs) + if err != nil { + return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) + } + + if err := user.Close(); err != nil { + return err + } + + if len(privKeys.Keys) == 0 { + return errNoKeys + } + + // Parse the change address. + changeAddr := ids.ShortEmpty + if len(args.ChangeAddr) > 0 { + var err error + if changeAddr, err = avax.ParseServiceAddress(s.addrManager, args.ChangeAddr); err != nil { + return fmt.Errorf(errInvalidChangeAddr, err) + } + } + + // Parse the consortium member address. + consortiumMemberAddress, err := avax.ParseServiceAddress(s.addrManager, args.ConsortiumMemberAddress) + if err != nil { + return fmt.Errorf("couldn't parse consortiumMemberAddress: %w", err) + } + + // Create the transaction + tx, err := s.vm.txBuilder.NewRegisterNodeTx( + args.OldNodeID, + args.NewNodeID, + consortiumMemberAddress, + privKeys.Keys, + changeAddr, + ) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + reply.TxID = tx.ID() + reply.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) + + errs := wrappers.Errs{} + errs.Add( + err, + s.vm.Builder.AddUnverifiedTx(tx), + ) + + return errs.Err +} + func (s *Service) getKeystoreKeys(args *api.JSONSpendHeader) (*secp256k1fx.Keychain, error) { // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.addrManager, args.From) diff --git a/vms/platformvm/metrics/camino_tx_metrics.go b/vms/platformvm/metrics/camino_tx_metrics.go index f12d8b461cbd..3485481c9902 100644 --- a/vms/platformvm/metrics/camino_tx_metrics.go +++ b/vms/platformvm/metrics/camino_tx_metrics.go @@ -33,6 +33,9 @@ func newCaminoTxMetrics( txMetrics: *txm, // Camino specific tx metrics numAddAddressStateTxs: newTxMetric(namespace, "add_address_state", registerer, &errs), + numDepositTxs: newTxMetric(namespace, "deposit", registerer, &errs), + numUnlockDepositTxs: newTxMetric(namespace, "unlock_deposit", registerer, &errs), + numRegisterNodeTx: newTxMetric(namespace, "register_node", registerer, &errs), } return m, errs.Err } diff --git a/vms/platformvm/msig/msig.go b/vms/platformvm/msig/msig.go new file mode 100644 index 000000000000..40447fae46d6 --- /dev/null +++ b/vms/platformvm/msig/msig.go @@ -0,0 +1,27 @@ +// Copyright (C) 2022, Chain4Travel AG. All rights reserved. +// See the file LICENSE for licensing terms. + +package msig + +import ( + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/platformvm/state" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" +) + +func GetOwner(state state.Chain, addr ids.ShortID) (*secp256k1fx.OutputOwners, error) { + msigOwner, err := state.GetMultisigOwner(addr) + if err != nil && err != database.ErrNotFound { + return nil, err + } + + if msigOwner != nil { + return &msigOwner.Owners, nil + } + + return &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{addr}, + }, nil +} diff --git a/vms/platformvm/state/camino.go b/vms/platformvm/state/camino.go index cc320e96f4e0..252b5105cffa 100644 --- a/vms/platformvm/state/camino.go +++ b/vms/platformvm/state/camino.go @@ -250,6 +250,14 @@ func (cs *caminoState) SyncGenesis(s *state, g *genesis.State) error { for _, consortiumMemberNode := range g.Camino.ConsortiumMembersNodeIDs { cs.SetNodeConsortiumMember(consortiumMemberNode.NodeID, &consortiumMemberNode.ConsortiumMemberAddress) + addrState, err := cs.GetAddressStates(consortiumMemberNode.ConsortiumMemberAddress) + if err != nil { + return err + } + cs.SetAddressStates( + consortiumMemberNode.ConsortiumMemberAddress, + addrState|txs.AddressStateRegisteredNodeBit, + ) } // adding deposit offers diff --git a/vms/platformvm/state/camino_diff.go b/vms/platformvm/state/camino_diff.go index d6ac7da49b61..d3b631a16b65 100644 --- a/vms/platformvm/state/camino_diff.go +++ b/vms/platformvm/state/camino_diff.go @@ -213,4 +213,8 @@ func (d *diff) ApplyCaminoState(baseState State) { for _, v := range d.caminoDiff.modifiedMultisigOwners { baseState.SetMultisigOwner(v) } + + for nodeID, addr := range d.caminoDiff.modifiedConsortiumMemberNodes { + baseState.SetNodeConsortiumMember(nodeID, addr) + } } diff --git a/vms/platformvm/state/mock_chain.go b/vms/platformvm/state/mock_chain.go index 05d938580261..f7c9d33c2f11 100644 --- a/vms/platformvm/state/mock_chain.go +++ b/vms/platformvm/state/mock_chain.go @@ -259,7 +259,6 @@ func (mr *MockChainMockRecorder) SetNodeConsortiumMember(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNodeConsortiumMember", reflect.TypeOf((*MockChain)(nil).SetNodeConsortiumMember), arg0, arg1) } - // GetNodeConsortiumMember mocks base method. func (m *MockChain) GetNodeConsortiumMember(arg0 ids.NodeID) (ids.ShortID, error) { m.ctrl.T.Helper() diff --git a/vms/platformvm/txs/builder/camino_builder.go b/vms/platformvm/txs/builder/camino_builder.go index b0e792295589..a2ec8e93d05b 100644 --- a/vms/platformvm/txs/builder/camino_builder.go +++ b/vms/platformvm/txs/builder/camino_builder.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/config" "github.com/ava-labs/avalanchego/vms/platformvm/fx" "github.com/ava-labs/avalanchego/vms/platformvm/locked" + "github.com/ava-labs/avalanchego/vms/platformvm/msig" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/utxo" @@ -58,6 +59,14 @@ type CaminoTxBuilder interface { keys []*crypto.PrivateKeySECP256K1R, changeAddr ids.ShortID, ) (*txs.Tx, error) + + NewRegisterNodeTx( + OldNodeID ids.NodeID, + NewNodeID ids.NodeID, + ConsortiumMemberAddress ids.ShortID, + keys []*crypto.PrivateKeySECP256K1R, + changeAddr ids.ShortID, + ) (*txs.Tx, error) } func NewCamino( @@ -176,7 +185,7 @@ func (b *caminoBuilder) NewAddSubnetValidatorTx( return tx, nil } - nodeSigners, err := getSigners(keys, ids.ShortID(nodeID)) + nodeSigners, err := getSigner(keys, ids.ShortID(nodeID)) if err != nil { return nil, err } @@ -327,19 +336,88 @@ func (b *caminoBuilder) NewUnlockDepositTx( return tx, tx.SyntacticVerify(b.ctx) } -func getSigners( +func (b *caminoBuilder) NewRegisterNodeTx( + oldNodeID ids.NodeID, + newNodeID ids.NodeID, + consortiumMemberAddress ids.ShortID, keys []*crypto.PrivateKeySECP256K1R, - address ids.ShortID, -) ([]*crypto.PrivateKeySECP256K1R, error) { - signer, found := secp256k1fx.NewKeychain(keys...).Get(address) - if !found { - return nil, fmt.Errorf("%w %s", errKeyMissing, address.String()) + changeAddr ids.ShortID, +) (*txs.Tx, error) { + ins, outs, signers, err := b.Lock(keys, 0, b.cfg.TxFee, locked.StateUnlocked, changeAddr) + if err != nil { + return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err) + } + + nodeSigners := []*crypto.PrivateKeySECP256K1R{} + if newNodeID != ids.EmptyNodeID { + nodeSigners, err = getSigner(keys, ids.ShortID(newNodeID)) + if err != nil { + return nil, err + } + } + signers = append(signers, nodeSigners) + + consortiumMemberOwner, err := msig.GetOwner(b.state, consortiumMemberAddress) + if err != nil { + return nil, err + } + + consortiumSigners, err := getSigners(keys, consortiumMemberOwner.Addrs) + if err != nil { + return nil, err + } + signers = append(signers, consortiumSigners) + + kc := secp256k1fx.NewKeychain(consortiumSigners...) + sigIndices, _, able := kc.Match(consortiumMemberOwner, b.clk.Unix()) + if !able { + return nil, fmt.Errorf("failed to get consortium member auth: %w", err) } - key, ok := signer.(*crypto.PrivateKeySECP256K1R) - if !ok { - return nil, errWrongNodeKeyType + utx := &txs.RegisterNodeTx{ + BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ + NetworkID: b.ctx.NetworkID, + BlockchainID: b.ctx.ChainID, + Ins: ins, + Outs: outs, + }}, + OldNodeID: oldNodeID, + NewNodeID: newNodeID, + ConsortiumMemberAuth: &secp256k1fx.Input{SigIndices: sigIndices}, + ConsortiumMemberAddress: consortiumMemberAddress, } - return []*crypto.PrivateKeySECP256K1R{key}, nil + tx, err := txs.NewSigned(utx, txs.Codec, signers) + if err != nil { + return nil, err + } + return tx, tx.SyntacticVerify(b.ctx) +} + +func getSigner( + keys []*crypto.PrivateKeySECP256K1R, + address ids.ShortID, +) ([]*crypto.PrivateKeySECP256K1R, error) { + return getSigners(keys, []ids.ShortID{address}) +} + +func getSigners( + keys []*crypto.PrivateKeySECP256K1R, + addresses []ids.ShortID, +) ([]*crypto.PrivateKeySECP256K1R, error) { + signers := make([]*crypto.PrivateKeySECP256K1R, len(addresses)) + for i, addr := range addresses { + signer, found := secp256k1fx.NewKeychain(keys...).Get(addr) + if !found { + return nil, fmt.Errorf("%w %s", errKeyMissing, addr.String()) + } + + key, ok := signer.(*crypto.PrivateKeySECP256K1R) + if !ok { + return nil, errWrongNodeKeyType + } + + signers[i] = key + } + return signers, nil } diff --git a/vms/platformvm/txs/camino_address_state_tx.go b/vms/platformvm/txs/camino_address_state_tx.go index 50c6ea712e8b..7572b1be0aa7 100644 --- a/vms/platformvm/txs/camino_address_state_tx.go +++ b/vms/platformvm/txs/camino_address_state_tx.go @@ -29,8 +29,8 @@ const ( AddressStateConsortiumBit = uint64(0b10000000000000000000000000000000000) AddressStateKycBits = uint64(0b11100000000000000000000000000000000) - AddressStateValidator = uint8(38) - AddressStateValidatorBits = uint64(0b100000000000000000000000000000000000000) + AddressStateRegisteredNode = uint8(38) + AddressStateRegisteredNodeBit = uint64(0b100000000000000000000000000000000000000) AddressStateMax = uint8(63) AddressStateValidBits = AddressStateRoleBits | AddressStateKycBits diff --git a/vms/platformvm/txs/camino_register_node_tx.go b/vms/platformvm/txs/camino_register_node_tx.go index e1593e641119..07e53955ffe6 100644 --- a/vms/platformvm/txs/camino_register_node_tx.go +++ b/vms/platformvm/txs/camino_register_node_tx.go @@ -15,7 +15,8 @@ import ( var ( _ UnsignedTx = (*RegisterNodeTx)(nil) - errNoNodeID = errors.New("no nodeID specified") + errNoNodeID = errors.New("no nodeID specified") + errConsortiumMemberAddrEmpty = errors.New("consortium member address is empty") ) // RegisterNodeTx is an unsigned registerNodeTx @@ -49,12 +50,18 @@ func (tx *RegisterNodeTx) SyntacticVerify(ctx *snow.Context) error { return nil case tx.NewNodeID == ids.EmptyNodeID && tx.OldNodeID == ids.EmptyNodeID: return errNoNodeID + case tx.ConsortiumMemberAddress == ids.ShortEmpty: + return errConsortiumMemberAddrEmpty } if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { return fmt.Errorf("failed to verify BaseTx: %w", err) } + if err := tx.ConsortiumMemberAuth.Verify(); err != nil { + return fmt.Errorf("failed to verify consortium member auth: %w", err) + } + // cache that this is valid tx.SyntacticallyVerified = true return nil diff --git a/vms/platformvm/txs/executor/camino_tx_executor.go b/vms/platformvm/txs/executor/camino_tx_executor.go index 15ad519bbb11..335b9d1e9ddd 100644 --- a/vms/platformvm/txs/executor/camino_tx_executor.go +++ b/vms/platformvm/txs/executor/camino_tx_executor.go @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/avalanchego/vms/components/verify" deposits "github.com/ava-labs/avalanchego/vms/platformvm/deposit" "github.com/ava-labs/avalanchego/vms/platformvm/locked" + "github.com/ava-labs/avalanchego/vms/platformvm/msig" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/utxo" @@ -42,6 +43,7 @@ var ( errDepositDurationToBig = errors.New("deposit duration is greater than deposit offer maximum duration") errSupplyOverflow = errors.New("resulting total supply would be more, than allowed maximum") errNotConsortiumMember = errors.New("address isn't consortium member") + errConsortiumMemberHasNode = errors.New("consortium member already has registered node") errConsortiumSignatureMissing = errors.New("wrong consortium's member signature") errNotNodeOwner = errors.New("node is registered for another consortium member address") ) @@ -125,7 +127,7 @@ func (e *CaminoStandardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error return err } - consortiumMemberOwner, err := e.getMSIGOwner(consortiumMemberAddress) + consortiumMemberOwner, err := msig.GetOwner(e.State, consortiumMemberAddress) if err != nil { return err } @@ -153,55 +155,52 @@ func (e *CaminoStandardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error return errStakeTooLong } - if !e.Backend.Bootstrapped.GetValue() { - return nil - } - - currentTimestamp := e.State.GetTimestamp() - // Ensure the proposed validator starts after the current time - startTime := tx.StartTime() - if !currentTimestamp.Before(startTime) { - return fmt.Errorf( - "%w: %s >= %s", - errTimestampNotBeforeStartTime, - currentTimestamp, - startTime, - ) - } + if e.Backend.Bootstrapped.GetValue() { + currentTimestamp := e.State.GetTimestamp() + // Ensure the proposed validator starts after the current time + startTime := tx.StartTime() + if !currentTimestamp.Before(startTime) { + return fmt.Errorf( + "%w: %s >= %s", + errTimestampNotBeforeStartTime, + currentTimestamp, + startTime, + ) + } - if _, err := GetValidator(e.State, constants.PrimaryNetworkID, tx.Validator.NodeID); err == nil { - return errValidatorExists - } else if err != database.ErrNotFound { - return fmt.Errorf( - "failed to find whether %s is a primary network validator: %w", - tx.Validator.NodeID, - err, - ) - } + if _, err := GetValidator(e.State, constants.PrimaryNetworkID, tx.Validator.NodeID); err == nil { + return errValidatorExists + } else if err != database.ErrNotFound { + return fmt.Errorf( + "failed to find whether %s is a primary network validator: %w", + tx.Validator.NodeID, + err, + ) + } - // Verify the flowcheck - if err := e.Backend.FlowChecker.VerifyLock( - tx, - e.State, - tx.Ins, - tx.Outs, - e.Tx.Creds, - e.Backend.Config.AddPrimaryNetworkValidatorFee, - e.Backend.Ctx.AVAXAssetID, - locked.StateBonded, - ); err != nil { - return fmt.Errorf("%w: %s", errFlowCheckFailed, err) - } + // Verify the flowcheck + if err := e.Backend.FlowChecker.VerifyLock( + tx, + e.State, + tx.Ins, + tx.Outs, + e.Tx.Creds, + e.Backend.Config.AddPrimaryNetworkValidatorFee, + e.Backend.Ctx.AVAXAssetID, + locked.StateBonded, + ); err != nil { + return fmt.Errorf("%w: %s", errFlowCheckFailed, err) + } - // Make sure the tx doesn't start too far in the future. This is done last - // to allow the verifier visitor to explicitly check for this error. - maxStartTime := currentTimestamp.Add(MaxFutureStartTime) - if startTime.After(maxStartTime) { - return errFutureStakeTime + // Make sure the tx doesn't start too far in the future. This is done last + // to allow the verifier visitor to explicitly check for this error. + maxStartTime := currentTimestamp.Add(MaxFutureStartTime) + if startTime.After(maxStartTime) { + return errFutureStakeTime + } } txID := e.Tx.ID() - newStaker, err := state.NewPendingStaker(txID, tx) if err != nil { return err @@ -631,9 +630,17 @@ func (e *CaminoStandardTxExecutor) RegisterNodeTx(tx *txs.RegisterNodeTx) error return errNotConsortiumMember } + newNodeIDNotEmpty := tx.NewNodeID != ids.EmptyNodeID + oldNodeIDNotEmpty := tx.OldNodeID != ids.EmptyNodeID + + if !oldNodeIDNotEmpty && newNodeIDNotEmpty && + consortiumMemberAddressState&txs.AddressStateRegisteredNodeBit != 0 { + return errConsortiumMemberHasNode + } + // verify consortium member cred - consortiumMemberOwner, err := e.getMSIGOwner(tx.ConsortiumMemberAddress) + consortiumMemberOwner, err := msig.GetOwner(e.State, tx.ConsortiumMemberAddress) if err != nil { return err } @@ -649,7 +656,6 @@ func (e *CaminoStandardTxExecutor) RegisterNodeTx(tx *txs.RegisterNodeTx) error // verify old nodeID ownership - oldNodeIDNotEmpty := tx.OldNodeID != ids.EmptyNodeID if oldNodeIDNotEmpty { oldNodeOwnerAddr, err := e.State.GetNodeConsortiumMember(tx.OldNodeID) if err != nil { @@ -662,7 +668,6 @@ func (e *CaminoStandardTxExecutor) RegisterNodeTx(tx *txs.RegisterNodeTx) error // verify new nodeID cred - newNodeIDNotEmpty := tx.NewNodeID != ids.EmptyNodeID if newNodeIDNotEmpty { if err := e.Backend.Fx.VerifyPermission( e.Tx.Unsigned, @@ -687,7 +692,7 @@ func (e *CaminoStandardTxExecutor) RegisterNodeTx(tx *txs.RegisterNodeTx) error e.Tx.Creds[:len(e.Tx.Creds)-2], // base tx creds e.Config.TxFee, e.Ctx.AVAXAssetID, - locked.StateUnlocked, + locked.StateBonded, ); err != nil { return err } @@ -709,6 +714,18 @@ func (e *CaminoStandardTxExecutor) RegisterNodeTx(tx *txs.RegisterNodeTx) error e.State.SetNodeConsortiumMember(tx.NewNodeID, &tx.ConsortiumMemberAddress) } + newConsortiumMemberAddressState := consortiumMemberAddressState + + if !oldNodeIDNotEmpty && newNodeIDNotEmpty { + newConsortiumMemberAddressState |= txs.AddressStateRegisteredNodeBit + } else if !newNodeIDNotEmpty { + newConsortiumMemberAddressState &^= txs.AddressStateRegisteredNodeBit + } + + if newConsortiumMemberAddressState != consortiumMemberAddressState { + e.State.SetAddressStates(tx.ConsortiumMemberAddress, newConsortiumMemberAddressState) + } + return nil } @@ -801,7 +818,7 @@ func verifyAccess(roles, statesBit uint64) error { if (roles & txs.AddressStateRoleKycBit) == 0 { return errInvalidRoles } - case (txs.AddressStateValidatorBits & statesBit) != 0: + case (txs.AddressStateRegisteredNodeBit & statesBit) != 0: if (roles & txs.AddressStateRoleValidatorBit) == 0 { return errInvalidRoles } @@ -823,19 +840,3 @@ func verifyAddrsOwner(addrs set.Set[ids.ShortID], owner *secp256k1fx.OutputOwner } return errors.New("missing signature") } - -func (e *CaminoStandardTxExecutor) getMSIGOwner(addr ids.ShortID) (*secp256k1fx.OutputOwners, error) { - msigOwner, err := e.State.GetMultisigOwner(addr) - if err != nil && err != database.ErrNotFound { - return nil, err - } - - if msigOwner != nil { - return &msigOwner.Owners, nil - } - - return &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{addr}, - }, nil -} diff --git a/vms/platformvm/txs/executor/camino_tx_executor_test.go b/vms/platformvm/txs/executor/camino_tx_executor_test.go index 334625f93350..134468f10647 100644 --- a/vms/platformvm/txs/executor/camino_tx_executor_test.go +++ b/vms/platformvm/txs/executor/camino_tx_executor_test.go @@ -222,7 +222,9 @@ func TestCaminoStandardTxExecutorAddValidatorTx(t *testing.T) { changeAddr: ids.ShortEmpty, } }, - preExecute: func(t *testing.T, tx *txs.Tx) {}, + preExecute: func(t *testing.T, tx *txs.Tx) { + env.state.SetNodeConsortiumMember(nodeID, &addr0) + }, expectedErr: errConsortiumSignatureMissing, }, "Not enough sigs from msig consortium member": {