gecko/vms/platformvm/service.go

1029 lines
33 KiB
Go
Raw Normal View History

2020-03-10 12:20:34 -07:00
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package platformvm
import (
"bytes"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"github.com/gorilla/rpc/v2/json2"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/json"
)
var (
errMissingDecisionBlock = errors.New("should have a decision block within the past two blocks")
errParsingID = errors.New("error parsing ID")
errGetAccount = errors.New("error retrieving account information")
errGetAccounts = errors.New("error getting accounts controlled by specified user")
errGetUser = errors.New("error while getting user. Does user exist?")
errNoMethodWithGenesis = errors.New("no method was provided but genesis data was provided")
errCreatingTransaction = errors.New("problem while creating transaction")
errNoDestination = errors.New("call is missing field 'stakeDestination'")
errNoSource = errors.New("call is missing field 'stakeSource'")
errGetStakeSource = errors.New("couldn't get account specified in 'stakeSource'")
)
var key *crypto.PrivateKeySECP256K1R
func init() {
cb58 := formatting.CB58{}
err := cb58.FromString("24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5")
if err != nil {
panic(err)
}
factory := crypto.FactorySECP256K1R{}
pk, err := factory.ToPrivateKey(cb58.Bytes)
if err != nil {
panic(err)
}
key = pk.(*crypto.PrivateKeySECP256K1R)
}
// Service defines the API calls that can be made to the platform chain
type Service struct{ vm *VM }
/*
******************************************************
******************* Get Subnets **********************
******************************************************
*/
// APISubnet is a representation of a subnet used in API calls
type APISubnet struct {
// ID of the subnet
ID ids.ID `json:"id"`
// Each element of [ControlKeys] the address of a public key.
// A transaction to add a validator to this subnet requires
// signatures from [Threshold] of these keys to be valid.
ControlKeys []ids.ShortID `json:"controlKeys"`
Threshold json.Uint16 `json:"threshold"`
}
// GetSubnetsArgs are the arguments to GetSubnet
type GetSubnetsArgs struct {
// IDs of the subnets to retrieve information about
// If omitted, gets all subnets
IDs []ids.ID `json:"ids"`
}
// GetSubnetsResponse is the response from calling GetSubnets
type GetSubnetsResponse struct {
// Each element is a subnet that exists
// Null if there are no subnets other than the default subnet
Subnets []APISubnet `json:"subnets"`
}
// GetSubnets returns the subnets whose ID are in [args.IDs]
// The response will not contain the default subnet
func (service *Service) GetSubnets(_ *http.Request, args *GetSubnetsArgs, response *GetSubnetsResponse) error {
subnets, err := service.vm.getSubnets(service.vm.DB) // all subnets
if err != nil {
return fmt.Errorf("error getting subnets from database: %v", err)
}
getAll := len(args.IDs) == 0
if getAll {
response.Subnets = make([]APISubnet, len(subnets))
for i, subnet := range subnets {
response.Subnets[i] = APISubnet{
ID: subnet.ID,
ControlKeys: subnet.ControlKeys,
Threshold: json.Uint16(subnet.Threshold),
}
}
return nil
}
idsSet := ids.Set{}
idsSet.Add(args.IDs...)
for _, subnet := range subnets {
if idsSet.Contains(subnet.ID) {
response.Subnets = append(response.Subnets,
APISubnet{
ID: subnet.ID,
ControlKeys: subnet.ControlKeys,
Threshold: json.Uint16(subnet.Threshold),
},
)
}
}
return nil
}
/*
******************************************************
**************** Get/Sample Validators ***************
******************************************************
*/
// GetCurrentValidatorsArgs are the arguments for calling GetCurrentValidators
type GetCurrentValidatorsArgs struct {
// Subnet we're listing the validators of
// If omitted, defaults to default subnet
SubnetID ids.ID `json:"subnetID"`
}
// GetCurrentValidatorsReply are the results from calling GetCurrentValidators
type GetCurrentValidatorsReply struct {
Validators []APIValidator `json:"validators"`
}
// GetCurrentValidators returns the list of current validators
func (service *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidatorsArgs, reply *GetCurrentValidatorsReply) error {
service.vm.Ctx.Log.Debug("GetCurrentValidators called")
if args.SubnetID.IsZero() {
args.SubnetID = DefaultSubnetID
}
validators, err := service.vm.getCurrentValidators(service.vm.DB, args.SubnetID)
if err != nil {
return fmt.Errorf("couldn't get validators of subnet with ID %s. Does it exist?", args.SubnetID)
}
reply.Validators = make([]APIValidator, validators.Len())
for i, tx := range validators.Txs {
vdr := tx.Vdr()
weight := json.Uint64(vdr.Weight())
if args.SubnetID.Equals(DefaultSubnetID) {
reply.Validators[i] = APIValidator{
ID: vdr.ID(),
StartTime: json.Uint64(tx.StartTime().Unix()),
EndTime: json.Uint64(tx.EndTime().Unix()),
StakeAmount: &weight,
}
} else {
reply.Validators[i] = APIValidator{
ID: vdr.ID(),
StartTime: json.Uint64(tx.StartTime().Unix()),
EndTime: json.Uint64(tx.EndTime().Unix()),
Weight: &weight,
}
}
}
return nil
}
// GetPendingValidatorsArgs are the arguments for calling GetPendingValidators
type GetPendingValidatorsArgs struct {
// Subnet we're getting the pending validators of
// If omitted, defaults to default subnet
SubnetID ids.ID `json:"subnetID"`
}
// GetPendingValidatorsReply are the results from calling GetPendingValidators
type GetPendingValidatorsReply struct {
Validators []APIValidator `json:"validators"`
}
// GetPendingValidators returns the list of current validators
func (service *Service) GetPendingValidators(_ *http.Request, args *GetPendingValidatorsArgs, reply *GetPendingValidatorsReply) error {
service.vm.Ctx.Log.Debug("GetPendingValidators called")
if args.SubnetID.IsZero() {
args.SubnetID = DefaultSubnetID
}
validators, err := service.vm.getPendingValidators(service.vm.DB, args.SubnetID)
if err != nil {
return fmt.Errorf("couldn't get validators of subnet with ID %s. Does it exist?", args.SubnetID)
}
reply.Validators = make([]APIValidator, validators.Len())
for i, tx := range validators.Txs {
vdr := tx.Vdr()
weight := json.Uint64(vdr.Weight())
if args.SubnetID.Equals(DefaultSubnetID) {
reply.Validators[i] = APIValidator{
ID: vdr.ID(),
StartTime: json.Uint64(tx.StartTime().Unix()),
EndTime: json.Uint64(tx.EndTime().Unix()),
StakeAmount: &weight,
}
} else {
reply.Validators[i] = APIValidator{
ID: vdr.ID(),
StartTime: json.Uint64(tx.StartTime().Unix()),
EndTime: json.Uint64(tx.EndTime().Unix()),
Weight: &weight,
}
}
}
return nil
}
// SampleValidatorsArgs are the arguments for calling SampleValidators
type SampleValidatorsArgs struct {
// Number of validators in the sample
Size json.Uint16 `json:"size"`
// ID of subnet to sample validators from
// If omitted, defaults to the default subnet
SubnetID ids.ID `json:"subnetID"`
}
// SampleValidatorsReply are the results from calling Sample
type SampleValidatorsReply struct {
Validators []ids.ShortID `json:"validators"`
}
// SampleValidators returns a sampling of the list of current validators
func (service *Service) SampleValidators(_ *http.Request, args *SampleValidatorsArgs, reply *SampleValidatorsReply) error {
service.vm.Ctx.Log.Debug("Sample called with {Size = %d}", args.Size)
if args.SubnetID.IsZero() {
args.SubnetID = DefaultSubnetID
}
validators, ok := service.vm.Validators.GetValidatorSet(args.SubnetID)
if !ok {
return fmt.Errorf("couldn't get validators of subnet with ID %s. Does it exist?", args.SubnetID)
}
sample := validators.Sample(int(args.Size))
if setLen := len(sample); setLen != int(args.Size) {
return fmt.Errorf("current number of validators (%d) is insufficient to sample %d validators", setLen, args.Size)
}
reply.Validators = make([]ids.ShortID, int(args.Size))
for i, vdr := range sample {
reply.Validators[i] = vdr.ID()
}
ids.SortShortIDs(reply.Validators)
return nil
}
/*
******************************************************
*************** Get/Create Accounts ******************
******************************************************
*/
// GetAccountArgs are the arguments for calling GetAccount
type GetAccountArgs struct {
// Address of the account we want the information about
Address ids.ShortID `json:"address"`
}
// GetAccountReply is the response from calling GetAccount
type GetAccountReply struct {
Address ids.ShortID `json:"address"`
Nonce json.Uint64 `json:"nonce"`
Balance json.Uint64 `json:"balance"`
}
// GetAccount details given account ID
func (service *Service) GetAccount(_ *http.Request, args *GetAccountArgs, reply *GetAccountReply) error {
account, err := service.vm.getAccount(service.vm.DB, args.Address)
if err != nil && err != database.ErrNotFound {
return errGetAccount
} else if err == database.ErrNotFound {
account = newAccount(args.Address, 0, 0)
}
reply.Address = account.Address
reply.Balance = json.Uint64(account.Balance)
reply.Nonce = json.Uint64(account.Nonce)
return nil
}
// ListAccountsArgs are the arguments to ListAccounts
type ListAccountsArgs struct {
// List all of the accounts controlled by this user
Username string `json:"username"`
Password string `json:"password"`
}
// ListAccountsReply is the reply from ListAccounts
type ListAccountsReply struct {
Accounts []APIAccount `json:"accounts"`
}
// ListAccounts lists all of the accounts controlled by [args.Username]
func (service *Service) ListAccounts(_ *http.Request, args *ListAccountsArgs, reply *ListAccountsReply) error {
service.vm.Ctx.Log.Debug("platform.listAccounts called for user '%s'", args.Username)
// db holds the user's info that pertains to the Platform Chain
userDB, err := service.vm.Ctx.Keystore.GetDatabase(args.Username, args.Password)
if err != nil {
return errGetUser
}
// The user
user := user{
db: userDB,
}
// IDs of accounts controlled by this user
accountIDs, err := user.getAccountIDs()
if err != nil {
return errGetAccounts
}
var accounts []APIAccount
for _, accountID := range accountIDs {
account, err := service.vm.getAccount(service.vm.DB, accountID) // Get account whose ID is [accountID]
if err != nil && err != database.ErrNotFound {
service.vm.Ctx.Log.Error("couldn't get account from database: %v", err)
continue
} else if err == database.ErrNotFound {
account = newAccount(accountID, 0, 0)
}
accounts = append(accounts, APIAccount{
Address: accountID,
Nonce: json.Uint64(account.Nonce),
Balance: json.Uint64(account.Balance),
})
}
reply.Accounts = accounts
return nil
}
// CreateAccountArgs are the arguments for calling CreateAccount
type CreateAccountArgs struct {
// User that will control the newly created account
Username string `json:"username"`
// That user's password
Password string `json:"password"`
// The private key that controls the new account.
// If omitted, will generate a new private key belonging
// to the user.
PrivateKey string `json:"privateKey"`
}
// CreateAccountReply are the response from calling CreateAccount
type CreateAccountReply struct {
// Address of the newly created account
Address ids.ShortID `json:"address"`
}
// CreateAccount creates a new account on the Platform Chain
// The account is controlled by [args.Username]
// The account's ID is [privKey].PublicKey().Address(), where [privKey] is a
// private key controlled by the user.
func (service *Service) CreateAccount(_ *http.Request, args *CreateAccountArgs, reply *CreateAccountReply) error {
service.vm.Ctx.Log.Debug("platform.createAccount called for user '%s'", args.Username)
// userDB holds the user's info that pertains to the Platform Chain
userDB, err := service.vm.Ctx.Keystore.GetDatabase(args.Username, args.Password)
if err != nil {
return errGetUser
}
// The user creating a new account
user := user{
db: userDB,
}
// private key that controls the new account
var privKey *crypto.PrivateKeySECP256K1R
// If no private key supplied in args, create a new one
if args.PrivateKey == "" {
privKeyInt, err := service.vm.factory.NewPrivateKey() // The private key that controls the new account
if err != nil { // The account ID is [private key].PublicKey().Address()
return errors.New("problem generating private key")
}
privKey = privKeyInt.(*crypto.PrivateKeySECP256K1R)
} else { // parse provided private key
byteFormatter := formatting.CB58{}
err := byteFormatter.FromString(args.PrivateKey)
if err != nil {
return errors.New("problem while parsing privateKey")
}
pk, err := service.vm.factory.ToPrivateKey(byteFormatter.Bytes)
if err != nil {
return errors.New("problem while parsing privateKey")
}
privKey = pk.(*crypto.PrivateKeySECP256K1R)
}
if err := user.putAccount(privKey); err != nil { // Save the private key
return errors.New("problem saving account")
}
reply.Address = privKey.PublicKey().Address()
return nil
}
type genericTx struct {
Tx interface{} `serialize:"true"`
}
/*
******************************************************
************ Add Validators to Subnets ***************
******************************************************
*/
// AddDefaultSubnetValidatorArgs are the arguments to AddDefaultSubnetValidator
type AddDefaultSubnetValidatorArgs struct {
APIDefaultSubnetValidator
// Next unused nonce of the account the staked $AVA and tx fee are paid from
PayerNonce json.Uint64 `json:"payerNonce"`
}
// AddDefaultSubnetValidatorResponse is the response from a call to AddDefaultSubnetValidator
type AddDefaultSubnetValidatorResponse struct {
// The unsigned transaction
UnsignedTx formatting.CB58 `json:"unsignedTx"`
}
// AddDefaultSubnetValidator returns an unsigned transaction to add a validator to the default subnet
// The returned unsigned transaction should be signed using Sign()
func (service *Service) AddDefaultSubnetValidator(_ *http.Request, args *AddDefaultSubnetValidatorArgs, reply *AddDefaultSubnetValidatorResponse) error {
service.vm.Ctx.Log.Debug("platform.AddDefaultSubnetValidator called")
if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID
args.ID = service.vm.Ctx.NodeID
}
// Create the transaction
tx := addDefaultSubnetValidatorTx{UnsignedAddDefaultSubnetValidatorTx: UnsignedAddDefaultSubnetValidatorTx{
DurationValidator: DurationValidator{
Validator: Validator{
NodeID: args.ID,
Wght: args.weight(),
},
Start: uint64(args.StartTime),
End: uint64(args.EndTime),
},
Nonce: uint64(args.PayerNonce),
Destination: args.Destination,
NetworkID: service.vm.Ctx.NetworkID,
Shares: uint32(args.DelegationFeeRate),
}}
txBytes, err := Codec.Marshal(genericTx{Tx: &tx})
if err != nil {
return fmt.Errorf("problem while creating transaction: %w", err)
}
reply.UnsignedTx.Bytes = txBytes
return nil
}
// AddDefaultSubnetDelegatorArgs are the arguments to AddDefaultSubnetDelegator
type AddDefaultSubnetDelegatorArgs struct {
APIValidator
Destination ids.ShortID `json:"destination"`
// Next unused nonce of the account the staked $AVA and tx fee are paid from
PayerNonce json.Uint64 `json:"payerNonce"`
}
// AddDefaultSubnetDelegatorResponse is the response from a call to AddDefaultSubnetDelegator
type AddDefaultSubnetDelegatorResponse struct {
// The unsigned transaction
UnsignedTx formatting.CB58 `json:"unsignedTx"`
}
// AddDefaultSubnetDelegator returns an unsigned transaction to add a delegator
// to the default subnet
// The returned unsigned transaction should be signed using Sign()
func (service *Service) AddDefaultSubnetDelegator(_ *http.Request, args *AddDefaultSubnetDelegatorArgs, reply *AddDefaultSubnetDelegatorResponse) error {
service.vm.Ctx.Log.Debug("platform.AddDefaultSubnetDelegator called")
if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID
args.ID = service.vm.Ctx.NodeID
}
// Create the transaction
tx := addDefaultSubnetDelegatorTx{UnsignedAddDefaultSubnetDelegatorTx: UnsignedAddDefaultSubnetDelegatorTx{
DurationValidator: DurationValidator{
Validator: Validator{
NodeID: args.ID,
Wght: args.weight(),
},
Start: uint64(args.StartTime),
End: uint64(args.EndTime),
},
NetworkID: service.vm.Ctx.NetworkID,
Nonce: uint64(args.PayerNonce),
Destination: args.Destination,
}}
txBytes, err := Codec.Marshal(genericTx{Tx: &tx})
if err != nil {
return fmt.Errorf("problem while creating transaction: %w", err)
}
reply.UnsignedTx.Bytes = txBytes
return nil
}
// AddNonDefaultSubnetValidatorArgs are the arguments to AddNonDefaultSubnetValidator
type AddNonDefaultSubnetValidatorArgs struct {
APIValidator
// ID of subnet to validate
SubnetID ids.ID `json:"subnetID"`
// Next unused nonce of the account the tx fee is paid from
PayerNonce json.Uint64 `json:"payerNonce"`
}
// AddNonDefaultSubnetValidatorResponse is the response from a call to AddNonDefaultSubnetValidator
type AddNonDefaultSubnetValidatorResponse struct {
// The unsigned transaction
UnsignedTx formatting.CB58 `json:"unsignedTx"`
}
// AddNonDefaultSubnetValidator adds a validator to a subnet other than the default subnet
// Returns the unsigned transaction, which must be signed using Sign
func (service *Service) AddNonDefaultSubnetValidator(_ *http.Request, args *AddNonDefaultSubnetValidatorArgs, response *AddNonDefaultSubnetValidatorResponse) error {
tx := addNonDefaultSubnetValidatorTx{
UnsignedAddNonDefaultSubnetValidatorTx: UnsignedAddNonDefaultSubnetValidatorTx{
SubnetValidator: SubnetValidator{
DurationValidator: DurationValidator{
Validator: Validator{
NodeID: args.APIValidator.ID,
Wght: args.weight(),
},
Start: uint64(args.StartTime),
End: uint64(args.EndTime),
},
Subnet: args.SubnetID,
},
NetworkID: service.vm.Ctx.NetworkID,
Nonce: uint64(args.PayerNonce),
},
ControlSigs: nil,
PayerSig: [crypto.SECP256K1RSigLen]byte{},
vm: nil,
id: ids.ID{},
senderID: ids.ShortID{},
bytes: nil,
}
txBytes, err := Codec.Marshal(genericTx{Tx: &tx})
if err != nil {
return errCreatingTransaction
}
response.UnsignedTx.Bytes = txBytes
return nil
}
/*
******************************************************
**************** Sign/Issue Txs **********************
******************************************************
*/
// SignArgs are the arguments to Sign
type SignArgs struct {
// The bytes to sign
// Must be the output of AddDefaultSubnetValidator
Tx formatting.CB58 `json:"tx"`
// The address of the key signing the bytes
Signer ids.ShortID `json:"signer"`
// User that controls Signer
Username string `json:"username"`
Password string `json:"password"`
}
// SignResponse is the response from Sign
type SignResponse struct {
// The signed bytes
Tx formatting.CB58
}
// Sign [args.bytes]
func (service *Service) Sign(_ *http.Request, args *SignArgs, reply *SignResponse) error {
service.vm.Ctx.Log.Debug("platform.sign called")
// Get the key of the Signer
db, err := service.vm.Ctx.Keystore.GetDatabase(args.Username, args.Password)
if err != nil {
return fmt.Errorf("couldn't get data for user '%s'. Does user exist?", args.Username)
}
user := user{db: db}
key, err := user.getKey(args.Signer) // Key of [args.Signer]
if err != nil {
return errDB
}
if !bytes.Equal(key.PublicKey().Address().Bytes(), args.Signer.Bytes()) { // sanity check
return errors.New("got unexpected key from database")
}
genTx := genericTx{}
if err := Codec.Unmarshal(args.Tx.Bytes, &genTx); err != nil {
return err
}
switch tx := genTx.Tx.(type) {
case *addDefaultSubnetValidatorTx:
genTx.Tx, err = service.signAddDefaultSubnetValidatorTx(tx, key)
case *addDefaultSubnetDelegatorTx:
genTx.Tx, err = service.signAddDefaultSubnetDelegatorTx(tx, key)
case *addNonDefaultSubnetValidatorTx:
genTx.Tx, err = service.signAddNonDefaultSubnetValidatorTx(tx, key)
case *CreateSubnetTx:
genTx.Tx, err = service.signCreateSubnetTx(tx, key)
default:
err = errors.New("Could not parse given tx. Must be one of: addDefaultSubnetValidatorTx, addNonDefaultSubnetValidatorTx, createSubnetTx")
}
if err != nil {
return err
}
reply.Tx.Bytes, err = Codec.Marshal(genTx)
return err
}
// Sign [unsigned] with [key]
func (service *Service) signAddDefaultSubnetValidatorTx(tx *addDefaultSubnetValidatorTx, key *crypto.PrivateKeySECP256K1R) (*addDefaultSubnetValidatorTx, error) {
service.vm.Ctx.Log.Debug("platform.signAddDefaultSubnetValidatorTx called")
// TODO: Should we check if tx is already signed?
unsignedIntf := interface{}(&tx.UnsignedAddDefaultSubnetValidatorTx)
unsignedTxBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return nil, fmt.Errorf("error serializing unsigned tx: %v", err)
}
sig, err := key.Sign(unsignedTxBytes)
if err != nil {
return nil, errors.New("error while signing")
}
if len(sig) != crypto.SECP256K1RSigLen {
return nil, fmt.Errorf("expected signature to be length %d but was length %d", crypto.SECP256K1RSigLen, len(sig))
}
copy(tx.Sig[:], sig)
return tx, nil
}
// Sign [unsigned] with [key]
func (service *Service) signAddDefaultSubnetDelegatorTx(tx *addDefaultSubnetDelegatorTx, key *crypto.PrivateKeySECP256K1R) (*addDefaultSubnetDelegatorTx, error) {
service.vm.Ctx.Log.Debug("platform.signAddDefaultSubnetValidatorTx called")
// TODO: Should we check if tx is already signed?
unsignedIntf := interface{}(&tx.UnsignedAddDefaultSubnetDelegatorTx)
unsignedTxBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return nil, fmt.Errorf("error serializing unsigned tx: %v", err)
}
sig, err := key.Sign(unsignedTxBytes)
if err != nil {
return nil, errors.New("error while signing")
}
if len(sig) != crypto.SECP256K1RSigLen {
return nil, fmt.Errorf("expected signature to be length %d but was length %d", crypto.SECP256K1RSigLen, len(sig))
}
copy(tx.Sig[:], sig)
return tx, nil
}
// Sign [xt] with [key]
func (service *Service) signCreateSubnetTx(tx *CreateSubnetTx, key *crypto.PrivateKeySECP256K1R) (*CreateSubnetTx, error) {
service.vm.Ctx.Log.Debug("platform.signAddDefaultSubnetValidatorTx called")
// TODO: Should we check if tx is already signed?
unsignedIntf := interface{}(&tx.UnsignedCreateSubnetTx)
unsignedTxBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return nil, fmt.Errorf("error serializing unsigned tx: %v", err)
}
sig, err := key.Sign(unsignedTxBytes)
if err != nil {
return nil, errors.New("error while signing")
}
if len(sig) != crypto.SECP256K1RSigLen {
return nil, fmt.Errorf("expected signature to be length %d but was length %d", crypto.SECP256K1RSigLen, len(sig))
}
copy(tx.Sig[:], sig)
return tx, nil
}
// Signs an unsigned or partially signed addNonDefaultSubnetValidatorTx with [key]
// If [key] is a control key for the subnet and there is an empty spot in tx.ControlSigs, signs there
// If [key] is a control key for the subnet and there is no empty spot in tx.ControlSigs, signs as payer
// If [key] is not a control key, sign as payer (account controlled by [key] pays the tx fee)
// Sorts tx.ControlSigs before returning
// Assumes each element of tx.ControlSigs is actually a signature, not just empty bytes
func (service *Service) signAddNonDefaultSubnetValidatorTx(tx *addNonDefaultSubnetValidatorTx, key *crypto.PrivateKeySECP256K1R) (*addNonDefaultSubnetValidatorTx, error) {
service.vm.Ctx.Log.Debug("platform.signAddNonDefaultSubnetValidatorTx called")
// Compute the byte repr. of the unsigned tx and the signature of [key] over it
unsignedIntf := interface{}(&tx.UnsignedAddNonDefaultSubnetValidatorTx)
unsignedTxBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return nil, fmt.Errorf("error serializing unsigned tx: %v", err)
}
sig, err := key.Sign(unsignedTxBytes)
if err != nil {
return nil, errors.New("error while signing")
}
if len(sig) != crypto.SECP256K1RSigLen {
return nil, fmt.Errorf("expected signature to be length %d but was length %d", crypto.SECP256K1RSigLen, len(sig))
}
// Get information about the subnet
subnet, err := service.vm.getSubnet(service.vm.DB, tx.SubnetID())
if err != nil {
return nil, fmt.Errorf("problem getting subnet information: %v", err)
}
// Find the location at which [key] should put its signature.
// If [key] is a control key for this subnet and there is an empty spot in tx.ControlSigs, sign there
// If [key] is a control key for this subnet and there is no empty spot in tx.ControlSigs, sign as payer
// If [key] is not a control key, sign as payer (account controlled by [key] pays the tx fee)
controlKeySet := ids.ShortSet{}
controlKeySet.Add(subnet.ControlKeys...)
isControlKey := controlKeySet.Contains(key.PublicKey().Address())
payerSigEmpty := tx.PayerSig == [crypto.SECP256K1RSigLen]byte{} // true if no key has signed to pay the tx fee
if isControlKey && len(tx.ControlSigs) != int(subnet.Threshold) { // Sign as controlSig
tx.ControlSigs = append(tx.ControlSigs, [crypto.SECP256K1RSigLen]byte{})
copy(tx.ControlSigs[len(tx.ControlSigs)-1][:], sig)
} else if payerSigEmpty { // sign as payer
copy(tx.PayerSig[:], sig)
} else {
return nil, errors.New("no place for key to sign")
}
return tx, nil
}
// IssueTxArgs are the arguments to IssueTx
type IssueTxArgs struct {
// Tx being sent to the network
Tx formatting.CB58 `json:"tx"`
}
// IssueTxResponse is the response from IssueTx
type IssueTxResponse struct {
// ID of transaction being sent to network
TxID ids.ID `json:"txID"`
}
// IssueTx issues the transaction [args.Tx] to the network
func (service *Service) IssueTx(_ *http.Request, args *IssueTxArgs, response *IssueTxResponse) error {
genTx := genericTx{}
if err := Codec.Unmarshal(args.Tx.Bytes, &genTx); err != nil {
return err
}
switch tx := genTx.Tx.(type) {
case TimedTx:
if err := tx.initialize(service.vm); err != nil {
return fmt.Errorf("error initializing tx: %s", err)
}
service.vm.unissuedEvents.Push(tx)
defer service.vm.resetTimer()
response.TxID = tx.ID()
return nil
case *CreateSubnetTx:
if err := tx.initialize(service.vm); err != nil {
return fmt.Errorf("error initializing tx: %s", err)
}
service.vm.unissuedDecisionTxs = append(service.vm.unissuedDecisionTxs, tx)
defer service.vm.resetTimer()
response.TxID = tx.ID
return nil
default:
return errors.New("Could not parse given tx. Must be one of: addDefaultSubnetValidatorTx, addDefaultSubnetDelegatorTx, addNonDefaultSubnetValidatorTx, createSubnetTx")
}
}
/*
******************************************************
**************** Create a Subnet *********************
******************************************************
*/
// CreateSubnetArgs are the arguments to CreateSubnet
type CreateSubnetArgs struct {
// The ID member of APISubnet is ignored
APISubnet
// Nonce of the account that pays the transaction fee
PayerNonce json.Uint64 `json:"payerNonce"`
}
// CreateSubnetResponse is the response from a call to CreateSubnet
type CreateSubnetResponse struct {
// Byte representation of the unsigned transaction to create a new subnet
UnsignedTx formatting.CB58 `json:"unsignedTx"`
}
// CreateSubnet returns an unsigned transaction to create a new subnet.
// The unsigned transaction must be signed with the key of [args.Payer]
func (service *Service) CreateSubnet(_ *http.Request, args *CreateSubnetArgs, response *CreateSubnetResponse) error {
service.vm.Ctx.Log.Debug("platform.createSubnet called")
// Create the transaction
tx := CreateSubnetTx{
UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{
NetworkID: service.vm.Ctx.NetworkID,
Nonce: uint64(args.PayerNonce),
ControlKeys: args.ControlKeys,
Threshold: uint16(args.Threshold),
},
key: nil,
Sig: [65]byte{},
bytes: nil,
}
txBytes, err := Codec.Marshal(genericTx{Tx: &tx})
if err != nil {
return errCreatingTransaction
}
response.UnsignedTx.Bytes = txBytes
return nil
}
/*
******************************************************
******** Create/get status of a blockchain ***********
******************************************************
*/
// CreateBlockchainArgs is the arguments for calling CreateBlockchain
type CreateBlockchainArgs struct {
// ID of the VM the new blockchain is running
VMID string `json:"vmID"`
// IDs of the FXs the VM is running
FxIDs []string `json:"fxIDs"`
// Human-readable name for the new blockchain, not necessarily unique
Name string `json:"name"`
// To generate the byte representation of the genesis data for this blockchain,
// a POST request with body [GenesisData] is made to the API method whose name is [Method], whose
// endpoint is [Endpoint]. See Platform Chain documentation for more info and examples.
Method string `json:"method"`
Endpoint string `json:"endpoint"`
GenesisData interface{} `json:"genesisData"`
}
// CreateGenesisReply is the reply from a call to CreateGenesis
type CreateGenesisReply struct {
Bytes formatting.CB58 `json:"bytes"`
}
// CreateBlockchainReply is the reply from calling CreateBlockchain
type CreateBlockchainReply struct {
BlockchainID ids.ID `json:"blockchainID"`
}
// CreateBlockchain issues a transaction to the network to create a new blockchain
func (service *Service) CreateBlockchain(_ *http.Request, args *CreateBlockchainArgs, reply *CreateBlockchainReply) error {
vmID, err := service.vm.ChainManager.LookupVM(args.VMID)
if err != nil {
return fmt.Errorf("no VM with ID '%s' found", args.VMID)
}
fxIDs := []ids.ID(nil)
for _, fxIDStr := range args.FxIDs {
fxID, err := service.vm.ChainManager.LookupVM(fxIDStr)
if err != nil {
return fmt.Errorf("no FX with ID '%s' found", fxIDStr)
}
fxIDs = append(fxIDs, fxID)
}
genesisBytes := []byte(nil)
if args.Method != "" {
buf, err := json2.EncodeClientRequest(args.Method, args.GenesisData)
if err != nil {
return fmt.Errorf("problem building blockchain genesis state: %w", err)
}
writer := httptest.NewRecorder()
service.vm.Ctx.HTTP.Call(
/*writer=*/ writer,
/*method=*/ "POST",
/*base=*/ args.VMID,
/*endpoint=*/ args.Endpoint,
/*body=*/ bytes.NewBuffer(buf),
/*headers=*/ map[string]string{
"Content-Type": "application/json",
},
)
result := CreateGenesisReply{}
if err := json2.DecodeClientResponse(writer.Body, &result); err != nil {
return fmt.Errorf("problem building blockchain genesis state: %w", err)
}
genesisBytes = result.Bytes.Bytes
} else if args.GenesisData != nil {
return errNoMethodWithGenesis
}
// TODO: Should use the key store to sign this transaction.
// TODO: Nonce shouldn't always be 0
tx, err := service.vm.newCreateChainTx(0, genesisBytes, vmID, fxIDs, args.Name, service.vm.Ctx.NetworkID, key)
if err != nil {
return fmt.Errorf("problem creating transaction: %w", err)
}
// Add this tx to the set of unissued txs
service.vm.unissuedDecisionTxs = append(service.vm.unissuedDecisionTxs, tx)
service.vm.resetTimer()
reply.BlockchainID = tx.ID()
return nil
}
// GetBlockchainStatusArgs is the arguments for calling GetBlockchainStatus
// [BlockchainID] is the blockchain to get the status of.
type GetBlockchainStatusArgs struct {
BlockchainID string `json:"blockchainID"`
}
// GetBlockchainStatusReply is the reply from calling GetBlockchainStatus
// [Status] is the blockchain's status.
type GetBlockchainStatusReply struct {
Status Status `json:"status"`
}
// GetBlockchainStatus gets the status of a blockchain with the ID [args.BlockchainID].
func (service *Service) GetBlockchainStatus(_ *http.Request, args *GetBlockchainStatusArgs, reply *GetBlockchainStatusReply) error {
_, err := service.vm.ChainManager.Lookup(args.BlockchainID)
if err == nil {
reply.Status = Validating
return nil
}
bID, err := ids.FromString(args.BlockchainID)
if err != nil {
return fmt.Errorf("problem parsing blockchainID '%s': %w", args.BlockchainID, err)
}
lastAcceptedID := service.vm.LastAccepted()
if exists, err := service.chainExists(lastAcceptedID, bID); err != nil {
return fmt.Errorf("problem looking up blockchain: %w", err)
} else if exists {
reply.Status = Created
return nil
}
preferred := service.vm.Preferred()
if exists, err := service.chainExists(preferred, bID); err != nil {
return fmt.Errorf("problem looking up blockchain: %w", err)
} else if exists {
reply.Status = Preferred
return nil
}
return nil
}
func (service *Service) chainExists(blockID ids.ID, chainID ids.ID) (bool, error) {
blockIntf, err := service.vm.getBlock(blockID)
if err != nil {
return false, err
}
block, ok := blockIntf.(decision)
if !ok {
block, ok = blockIntf.Parent().(decision)
if !ok {
return false, errMissingDecisionBlock
}
}
db := block.onAccept()
chains, err := service.vm.getChains(db)
for _, chain := range chains {
if chain.ID().Equals(chainID) {
return true, nil
}
}
return false, nil
}