diff --git a/vms/platformvm/create_chain_tx.go b/vms/platformvm/create_chain_tx.go index 74bd3f0..dc9ec0e 100644 --- a/vms/platformvm/create_chain_tx.go +++ b/vms/platformvm/create_chain_tx.go @@ -15,8 +15,10 @@ import ( ) var ( - errInvalidVMID = errors.New("invalid VM ID") - errFxIDsNotSortedAndUnique = errors.New("feature extensions IDs must be sorted and unique") + errInvalidVMID = errors.New("invalid VM ID") + errFxIDsNotSortedAndUnique = errors.New("feature extensions IDs must be sorted and unique") + errControlSigsNotSortedAndUnique = errors.New("control signatures must be sorted and unique") + errControlSigsNil = errors.New("control signatures are nil. (Should be empty slice if there are none.)") ) // UnsignedCreateChainTx is an unsigned CreateChainTx @@ -24,6 +26,9 @@ type UnsignedCreateChainTx struct { // ID of the network this blockchain exists on NetworkID uint32 `serialize:"true"` + // ID of the Subnet that validates this blockchain + SubnetID ids.ID `serialize:"true"` + // Next unused nonce of account paying the transaction fee for this transaction. // Currently unused, as there are no tx fees. Nonce uint64 `serialize:"true"` @@ -37,7 +42,7 @@ type UnsignedCreateChainTx struct { // IDs of the feature extensions running on the new chain FxIDs []ids.ID `serialize:"true"` - // Byte representation of state of the new chain + // Byte representation of genesis state of the new chain GenesisData []byte `serialize:"true"` } @@ -45,11 +50,19 @@ type UnsignedCreateChainTx struct { type CreateChainTx struct { UnsignedCreateChainTx `serialize:"true"` - Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"` + // Address of the account that provides the transaction fee + // Set in SemanticVerify + PayerAddress ids.ShortID + + // Signature of key whose account provides the transaction fee + PayerSig [crypto.SECP256K1RSigLen]byte `serialize:"true"` + + // Signatures from Subnet's control keys + // Should not empty slice, not nil, if there are no control sigs + ControlSigs [][crypto.SECP256K1RSigLen]byte `serialize:"true"` vm *VM id ids.ID - key crypto.PublicKey // public key of transaction signer bytes []byte } @@ -64,10 +77,6 @@ func (tx *CreateChainTx) initialize(vm *VM) error { // ID of this transaction func (tx *CreateChainTx) ID() ids.ID { return tx.id } -// Key returns the public key of the signer of this transaction -// Precondition: tx.Verify() has been called and returned nil -func (tx *CreateChainTx) Key() crypto.PublicKey { return tx.key } - // Bytes returns the byte representation of a CreateChainTx func (tx *CreateChainTx) Bytes() []byte { return tx.bytes } @@ -77,8 +86,8 @@ func (tx *CreateChainTx) SyntacticVerify() error { switch { case tx == nil: return errNilTx - case tx.key != nil: - return nil // Only verify the transaction once + case !tx.PayerAddress.IsZero(): // Only verify the transaction once + return nil case tx.NetworkID != tx.vm.Ctx.NetworkID: // verify the transaction is on this network return errWrongNetworkID case tx.id.IsZero(): @@ -87,6 +96,10 @@ func (tx *CreateChainTx) SyntacticVerify() error { return errInvalidVMID case !ids.IsSortedAndUniqueIDs(tx.FxIDs): return errFxIDsNotSortedAndUnique + case tx.ControlSigs == nil: + return errControlSigsNil + case !crypto.IsSortedAndUniqueSECP2561RSigs(tx.ControlSigs): + return errControlSigsNotSortedAndUnique } unsignedIntf := interface{}(&tx.UnsignedCreateChainTx) @@ -95,11 +108,11 @@ func (tx *CreateChainTx) SyntacticVerify() error { return err } - key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:]) + payerKey, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.PayerSig[:]) if err != nil { return err } - tx.key = key + tx.PayerAddress = payerKey.Address() return nil } @@ -125,11 +138,11 @@ func (tx *CreateChainTx) SemanticVerify(db database.Database) (func(), error) { } // Deduct tx fee from payer's account - account, err := tx.vm.getAccount(db, tx.Key().Address()) + account, err := tx.vm.getAccount(db, tx.PayerAddress) if err != nil { return nil, err } - account, err = account.Remove(0, tx.Nonce) + account, err = account.Remove(txFee, tx.Nonce) if err != nil { return nil, err } @@ -137,10 +150,56 @@ func (tx *CreateChainTx) SemanticVerify(db database.Database) (func(), error) { return nil, err } + // Verify that this transaction has sufficient control signatures + subnets, err := tx.vm.getSubnets(db) // all subnets that exist + if err != nil { + return nil, err + } + var subnet *CreateSubnetTx // the subnet that will validate the new chain + for _, sn := range subnets { + if sn.ID.Equals(tx.SubnetID) { + subnet = sn + break + } + } + if subnet == nil { + return nil, fmt.Errorf("there is no subnet with ID %s", tx.SubnetID) + } + if len(tx.ControlSigs) != int(subnet.Threshold) { + return nil, fmt.Errorf("expected tx to have %d control sigs but has %d", subnet.Threshold, len(tx.ControlSigs)) + } + + unsignedIntf := interface{}(&tx.UnsignedCreateChainTx) + unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte representation of the unsigned transaction + if err != nil { + return nil, err + } + unsignedBytesHash := hashing.ComputeHash256(unsignedBytes) + + // Each element is ID of key that signed this tx + controlIDs := make([]ids.ShortID, len(tx.ControlSigs)) + for i, sig := range tx.ControlSigs { + key, err := tx.vm.factory.RecoverHashPublicKey(unsignedBytesHash, sig[:]) + if err != nil { + return nil, err + } + controlIDs[i] = key.Address() + } + + // Verify each control signature on this tx is from a control key + controlKeys := ids.ShortSet{} + controlKeys.Add(subnet.ControlKeys...) + for _, controlID := range controlIDs { + if !controlKeys.Contains(controlID) { + return nil, errors.New("tx has control signature from key not in subnet's ControlKeys") + } + } + // If this proposal is committed, create the new blockchain using the chain manager onAccept := func() { chainParams := chains.ChainParameters{ ID: tx.ID(), + SubnetID: tx.SubnetID, GenesisData: tx.GenesisData, VMAlias: tx.VMID.String(), } @@ -166,10 +225,14 @@ func (chains createChainList) Bytes() []byte { return bytes } -func (vm *VM) newCreateChainTx(nonce uint64, genesisData []byte, vmID ids.ID, fxIDs []ids.ID, chainName string, networkID uint32, key *crypto.PrivateKeySECP256K1R) (*CreateChainTx, error) { +func (vm *VM) newCreateChainTx(nonce uint64, subnetID ids.ID, genesisData []byte, + vmID ids.ID, fxIDs []ids.ID, chainName string, networkID uint32, + controlKeys []*crypto.PrivateKeySECP256K1R, + payerKey *crypto.PrivateKeySECP256K1R) (*CreateChainTx, error) { tx := &CreateChainTx{ UnsignedCreateChainTx: UnsignedCreateChainTx{ NetworkID: networkID, + SubnetID: subnetID, Nonce: nonce, GenesisData: genesisData, VMID: vmID, @@ -178,17 +241,33 @@ func (vm *VM) newCreateChainTx(nonce uint64, genesisData []byte, vmID ids.ID, fx }, } + // Generate byte repr. of unsigned transaction unsignedIntf := interface{}(&tx.UnsignedCreateChainTx) - unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction + unsignedBytes, err := Codec.Marshal(&unsignedIntf) if err != nil { return nil, err } + unsignedBytesHash := hashing.ComputeHash256(unsignedBytes) + + // Sign the tx with control keys + tx.ControlSigs = make([][crypto.SECP256K1RSigLen]byte, len(controlKeys)) + for i, key := range controlKeys { + sig, err := key.SignHash(unsignedBytesHash) + if err != nil { + return nil, err + } + copy(tx.ControlSigs[i][:], sig) + } - sig, err := key.Sign(unsignedBytes) + // Sort the control signatures + crypto.SortSECP2561RSigs(tx.ControlSigs) + + // Sign with the payer key + payerSig, err := payerKey.Sign(unsignedBytes) if err != nil { return nil, err } - copy(tx.Sig[:], sig) + copy(tx.PayerSig[:], payerSig) return tx, tx.initialize(vm) } diff --git a/vms/platformvm/create_chain_tx_test.go b/vms/platformvm/create_chain_tx_test.go index 8c555c6..ee3d3bb 100644 --- a/vms/platformvm/create_chain_tx_test.go +++ b/vms/platformvm/create_chain_tx_test.go @@ -6,8 +6,8 @@ package platformvm import ( "testing" - "github.com/ava-labs/gecko/database/versiondb" "github.com/ava-labs/gecko/ids" + "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/vms/avm" ) @@ -24,18 +24,19 @@ func TestCreateChainTxSyntacticVerify(t *testing.T) { // Case 2: network ID is wrong tx, err := vm.newCreateChainTx( defaultNonce+1, + testSubnet1.ID, nil, avm.ID, nil, "chain name", testNetworkID+1, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, ) if err != nil { t.Fatal(err) } err = tx.SyntacticVerify() - t.Log(err) if err == nil { t.Fatal("should've errored because network ID is wrong") } @@ -43,11 +44,13 @@ func TestCreateChainTxSyntacticVerify(t *testing.T) { // case 3: tx ID is empty tx, err = vm.newCreateChainTx( defaultNonce+1, + testSubnet1.ID, nil, avm.ID, nil, "chain name", testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, ) if err != nil { @@ -61,11 +64,13 @@ func TestCreateChainTxSyntacticVerify(t *testing.T) { // Case 4: vm ID is empty tx, err = vm.newCreateChainTx( defaultNonce+1, + testSubnet1.ID, nil, avm.ID, nil, "chain name", testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, ) if err != nil { @@ -75,62 +80,209 @@ func TestCreateChainTxSyntacticVerify(t *testing.T) { if err := tx.SyntacticVerify(); err == nil { t.Fatal("should've errored because tx ID is empty") } -} -func TestSemanticVerify(t *testing.T) { - vm := defaultVM() - - // create a tx - tx, err := vm.newCreateChainTx( + // Case 5: Control sigs not sorted + tx, err = vm.newCreateChainTx( defaultNonce+1, + testSubnet1.ID, nil, avm.ID, nil, "chain name", testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + defaultKey, + ) + if err != nil { + t.Fatal(err) + } + // Reverse signature order + tx.ControlSigs[0], tx.ControlSigs[1] = tx.ControlSigs[1], tx.ControlSigs[0] + if err := tx.SyntacticVerify(); err == nil { + t.Fatal("should've errored because control sigs not sorted") + } + + // Case 6: Control sigs not unique + tx, err = vm.newCreateChainTx( + defaultNonce+1, + testSubnet1.ID, + nil, + avm.ID, + nil, + "chain name", + testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + defaultKey, + ) + if err != nil { + t.Fatal(err) + } + tx.ControlSigs[0] = tx.ControlSigs[1] + if err := tx.SyntacticVerify(); err == nil { + t.Fatal("should've errored because control sigs not unique") + } + + // Case 7: Control sigs are nil + tx, err = vm.newCreateChainTx( + defaultNonce+1, + testSubnet1.ID, + nil, + avm.ID, + nil, + "chain name", + testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + defaultKey, + ) + if err != nil { + t.Fatalf("should have passed verification but got %v", err) + } + tx.ControlSigs = nil + if err := tx.SyntacticVerify(); err == nil { + t.Fatal("should've errored because control sigs are nil") + } + + // Case 8: Valid tx passes syntactic verification + tx, err = vm.newCreateChainTx( + defaultNonce+1, + testSubnet1.ID, + nil, + avm.ID, + nil, + "chain name", + testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + defaultKey, + ) + if err != nil { + t.Fatalf("should have passed verification but got %v", err) + } +} + +// Ensure SemanticVerify fails when there are not enough control sigs +func TestCreateChainTxInsufficientControlSigs(t *testing.T) { + vm := defaultVM() + + // Case 1: No control sigs (2 are needed) + tx, err := vm.newCreateChainTx( + defaultNonce+1, + testSubnet1.ID, + nil, + avm.ID, + nil, + "chain name", + testNetworkID, + nil, defaultKey, ) if err != nil { t.Fatal(err) } - newDB := versiondb.New(vm.DB) - - _, err = tx.SemanticVerify(newDB) - if err != nil { - t.Fatal(err) + _, err = tx.SemanticVerify(vm.DB) + if err == nil { + t.Fatal("should have errored because there are no control sigs") } - chains, err := vm.getChains(newDB) - if err != nil { - t.Fatal(err) - } - for _, c := range chains { - if c.ID().Equals(tx.ID()) { - return - } - } - t.Fatalf("Should have added the chain to the set of chains") -} - -func TestSemanticVerifyAlreadyExisting(t *testing.T) { - vm := defaultVM() - - // create a tx - tx, err := vm.newCreateChainTx( + // Case 2: 1 control sig (2 are needed) + tx, err = vm.newCreateChainTx( defaultNonce+1, + testSubnet1.ID, nil, avm.ID, nil, "chain name", testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0]}, defaultKey, ) if err != nil { t.Fatal(err) } - // put the chain in existing chain + _, err = tx.SemanticVerify(vm.DB) + if err == nil { + t.Fatal("should have errored because there are no control sigs") + } +} + +// Ensure SemanticVerify fails when an incorrect control signature is given +func TestCreateChainTxWrongControlSig(t *testing.T) { + vm := defaultVM() + + // Generate new, random key to sign tx with + factory := crypto.FactorySECP256K1R{} + key, err := factory.NewPrivateKey() + if err != nil { + t.Fatal(err) + } + + tx, err := vm.newCreateChainTx( + defaultNonce+1, + testSubnet1.ID, + nil, + avm.ID, + nil, + "chain name", + testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], key.(*crypto.PrivateKeySECP256K1R)}, + defaultKey, + ) + if err != nil { + t.Fatal(err) + } + + _, err = tx.SemanticVerify(vm.DB) + if err == nil { + t.Fatal("should have errored because incorrect control sig given") + } +} + +// Ensure SemanticVerify fails when the Subnet the blockchain specifies as +// its validator set doesn't exist +func TestCreateChainTxNoSuchSubnet(t *testing.T) { + vm := defaultVM() + + tx, err := vm.newCreateChainTx( + defaultNonce+1, + ids.NewID([32]byte{1, 9, 124, 11, 20}), // pick some random ID for subnet + nil, + avm.ID, + nil, + "chain name", + testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + defaultKey, + ) + if err != nil { + t.Fatal(err) + } + _, err = tx.SemanticVerify(vm.DB) + if err == nil { + t.Fatal("should have errored because Subnet doesn't exist") + } +} + +func TestCreateChainTxAlreadyExists(t *testing.T) { + vm := defaultVM() + + // create a tx + tx, err := vm.newCreateChainTx( + defaultNonce+1, + testSubnet1.ID, + nil, + avm.ID, + nil, + "chain name", + testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + defaultKey, + ) + if err != nil { + t.Fatal(err) + } + + // put the chain in existing chain list if err := vm.putChains(vm.DB, []*CreateChainTx{tx}); err != nil { t.Fatal(err) } @@ -140,3 +292,29 @@ func TestSemanticVerifyAlreadyExisting(t *testing.T) { t.Fatalf("should have failed because there is already a chain with ID %s", tx.id) } } + +// Ensure valid tx passes semanticVerify +func TestCreateChainTxValid(t *testing.T) { + vm := defaultVM() + + // create a valid tx + tx, err := vm.newCreateChainTx( + defaultNonce+1, + testSubnet1.ID, + nil, + avm.ID, + nil, + "chain name", + testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + defaultKey, + ) + if err != nil { + t.Fatal(err) + } + + _, err = tx.SemanticVerify(vm.DB) + if err != nil { + t.Fatalf("expected tx to pass verification but got error: %v", err) + } +} diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 4842ff1..4547736 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -8,9 +8,6 @@ import ( "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" @@ -32,22 +29,6 @@ var ( 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 } @@ -642,8 +623,10 @@ func (service *Service) Sign(_ *http.Request, args *SignArgs, reply *SignRespons genTx.Tx, err = service.signAddNonDefaultSubnetValidatorTx(tx, key) case *CreateSubnetTx: genTx.Tx, err = service.signCreateSubnetTx(tx, key) + case *CreateChainTx: + genTx.Tx, err = service.signCreateChainTx(tx, key) default: - err = errors.New("Could not parse given tx. Must be one of: addDefaultSubnetValidatorTx, addNonDefaultSubnetValidatorTx, createSubnetTx") + err = errors.New("Could not parse given tx") } if err != nil { return err @@ -773,6 +756,59 @@ func (service *Service) signAddNonDefaultSubnetValidatorTx(tx *addNonDefaultSubn return tx, nil } +// Signs an unsigned or partially signed CreateChainTx 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) signCreateChainTx(tx *CreateChainTx, key *crypto.PrivateKeySECP256K1R) (*CreateChainTx, error) { + service.vm.Ctx.Log.Debug("platform.signCreateChainTx called") + + // Compute the byte repr. of the unsigned tx and the signature of [key] over it + unsignedIntf := interface{}(&tx.UnsignedCreateChainTx) + 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") + } + + crypto.SortSECP2561RSigs(tx.ControlSigs) + + return tx, nil +} + // IssueTxArgs are the arguments to IssueTx type IssueTxArgs struct { // Tx being sent to the network @@ -860,7 +896,6 @@ func (service *Service) CreateSubnet(_ *http.Request, args *CreateSubnetArgs, re response.UnsignedTx.Bytes = txBytes return nil - } /* @@ -871,6 +906,9 @@ func (service *Service) CreateSubnet(_ *http.Request, args *CreateSubnetArgs, re // CreateBlockchainArgs is the arguments for calling CreateBlockchain type CreateBlockchainArgs struct { + // ID of Subnet that validates the new blockchain + SubnetID ids.ID + // ID of the VM the new blockchain is running VMID string `json:"vmID"` @@ -880,26 +918,21 @@ type CreateBlockchainArgs struct { // 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"` -} + // Next unused nonce of the account paying the transaction fee + PayerNonce json.Uint64 `json:"payerNonce"` -// CreateGenesisReply is the reply from a call to CreateGenesis -type CreateGenesisReply struct { - Bytes formatting.CB58 `json:"bytes"` + // Genesis state of the blockchain being created + GenesisData formatting.CB58 `json:"genesisData"` } // CreateBlockchainReply is the reply from calling CreateBlockchain type CreateBlockchainReply struct { - BlockchainID ids.ID `json:"blockchainID"` + UnsignedTx formatting.CB58 `json:"unsignedTx"` } -// CreateBlockchain issues a transaction to the network to create a new blockchain -func (service *Service) CreateBlockchain(_ *http.Request, args *CreateBlockchainArgs, reply *CreateBlockchainReply) error { +// CreateBlockchain returns an unsigned transaction to create a new blockchain +// Must be signed with the Subnet's control keys and with a key that pays the transaction fee before issuance +func (service *Service) CreateBlockchain(_ *http.Request, args *CreateBlockchainArgs, response *CreateBlockchainReply) error { vmID, err := service.vm.ChainManager.LookupVM(args.VMID) if err != nil { return fmt.Errorf("no VM with ID '%s' found", args.VMID) @@ -914,47 +947,30 @@ func (service *Service) CreateBlockchain(_ *http.Request, args *CreateBlockchain 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 + tx := CreateChainTx{ + UnsignedCreateChainTx: UnsignedCreateChainTx{ + NetworkID: service.vm.Ctx.NetworkID, + SubnetID: args.SubnetID, + Nonce: uint64(args.PayerNonce), + ChainName: args.Name, + VMID: vmID, + FxIDs: fxIDs, + GenesisData: args.GenesisData.Bytes, + }, + PayerAddress: ids.ShortID{}, + PayerSig: [crypto.SECP256K1RSigLen]byte{}, + ControlSigs: nil, + vm: nil, + id: ids.ID{}, + bytes: nil, } - // 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) + txBytes, err := Codec.Marshal(genericTx{Tx: &tx}) if err != nil { - return fmt.Errorf("problem creating transaction: %w", err) + return errCreatingTransaction } - // Add this tx to the set of unissued txs - service.vm.unissuedDecisionTxs = append(service.vm.unissuedDecisionTxs, tx) - service.vm.resetTimer() - - reply.BlockchainID = tx.ID() - + response.UnsignedTx.Bytes = txBytes return nil } diff --git a/vms/platformvm/static_service.go b/vms/platformvm/static_service.go index cdfd1d7..11c46a0 100644 --- a/vms/platformvm/static_service.go +++ b/vms/platformvm/static_service.go @@ -9,6 +9,7 @@ import ( "net/http" "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" ) @@ -74,11 +75,13 @@ type APIDefaultSubnetValidator struct { // [VMID] is the ID of the VM this chain runs. // [FxIDs] are the IDs of the Fxs the chain supports. // [Name] is a human-readable, non-unique name for the chain. +// [SubnetID] is the ID of the subnet that validates the chain type APIChain struct { GenesisData formatting.CB58 `json:"genesisData"` VMID ids.ID `json:"vmID"` FxIDs []ids.ID `json:"fxIDs"` Name string `json:"name"` + SubnetID ids.ID `json:"subnetID"` } // BuildGenesisArgs are the arguments used to create @@ -134,8 +137,8 @@ func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, repl return errAccountHasNoValue } accounts = append(accounts, newAccount( - account.Address, // ID - 0, // nonce + account.Address, // ID + 0, // nonce uint64(account.Balance), // balance )) } @@ -182,12 +185,15 @@ func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, repl tx := &CreateChainTx{ UnsignedCreateChainTx: UnsignedCreateChainTx{ NetworkID: uint32(args.NetworkID), + SubnetID: chain.SubnetID, Nonce: 0, ChainName: chain.Name, VMID: chain.VMID, FxIDs: chain.FxIDs, GenesisData: chain.GenesisData.Bytes, }, + ControlSigs: [][crypto.SECP256K1RSigLen]byte{}, + PayerSig: [crypto.SECP256K1RSigLen]byte{}, } if err := tx.initialize(nil); err != nil { return err diff --git a/vms/platformvm/static_service_test.go b/vms/platformvm/static_service_test.go index d1bdc4e..084fc2a 100644 --- a/vms/platformvm/static_service_test.go +++ b/vms/platformvm/static_service_test.go @@ -81,6 +81,7 @@ func TestBuildGenesis(t *testing.T) { Destination: addr, } chains := APIChain{ + SubnetID: DefaultSubnetID, GenesisData: genesisData, VMID: vmID, Name: "My Favorite Episode", diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index 67c0084..66acf1b 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -35,16 +35,17 @@ var ( // each key corresponds to an account that has $AVA and a genesis validator keys []*crypto.PrivateKeySECP256K1R - // amount all genesis validators stake + // amount all genesis validators stake in defaultVM defaultStakeAmount uint64 - // balance of accounts that exist at genesis + // balance of accounts that exist at genesis in defaultVM defaultBalance = 100 * MinimumStakeAmount // At genesis this account has AVA and is validating the default subnet defaultKey *crypto.PrivateKeySECP256K1R - // non-default subnet that exists at genesis in defaultVM + // non-default Subnet that exists at genesis in defaultVM + // Its controlKeys are keys[0], keys[1], keys[2] testSubnet1 *CreateSubnetTx testSubnet1ControlKeys []*crypto.PrivateKeySECP256K1R ) @@ -132,7 +133,7 @@ func defaultVM() *VM { testNetworkID, 0, []ids.ShortID{keys[0].PublicKey().Address(), keys[1].PublicKey().Address(), keys[2].PublicKey().Address()}, // control keys are keys[0], keys[1], keys[2] - 2, // 2 sigs from keys[0], keys[1], keys[2] needed to add validator to this subnet + 2, // threshold; 2 sigs from keys[0], keys[1], keys[2] needed to add validator to this subnet keys[0], ) if err != nil { @@ -761,11 +762,13 @@ func TestCreateChain(t *testing.T) { tx, err := vm.newCreateChainTx( defaultNonce+1, + testSubnet1.ID, nil, timestampvm.ID, nil, - "name ", + "name", testNetworkID, + []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, keys[0], ) if err != nil { @@ -802,7 +805,7 @@ func TestCreateChain(t *testing.T) { } // Verify tx fee was deducted - account, err := vm.getAccount(vm.DB, tx.Key().Address()) + account, err := vm.getAccount(vm.DB, tx.PayerAddress) if err != nil { t.Fatal(err) }