mirror of https://github.com/poanetwork/gecko.git
263 lines
7.6 KiB
Go
263 lines
7.6 KiB
Go
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package platformvm
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"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/hashing"
|
|
)
|
|
|
|
var (
|
|
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")
|
|
)
|
|
|
|
// UnsignedCreateChainTx is an unsigned CreateChainTx
|
|
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"`
|
|
|
|
// A human readable name for the chain; need not be unique
|
|
ChainName string `serialize:"true"`
|
|
|
|
// ID of the VM running on the new chain
|
|
VMID ids.ID `serialize:"true"`
|
|
|
|
// IDs of the feature extensions running on the new chain
|
|
FxIDs []ids.ID `serialize:"true"`
|
|
|
|
// Byte representation of genesis state of the new chain
|
|
GenesisData []byte `serialize:"true"`
|
|
}
|
|
|
|
// CreateChainTx is a proposal to create a chain
|
|
type CreateChainTx struct {
|
|
UnsignedCreateChainTx `serialize:"true"`
|
|
|
|
// Address of the account that provides the transaction fee
|
|
// Set in SemanticVerify
|
|
PayerAddress ids.ShortID
|
|
|
|
// Signatures from Subnet's control keys
|
|
// Should not empty slice, not nil, if there are no control sigs
|
|
ControlSigs [][crypto.SECP256K1RSigLen]byte `serialize:"true"`
|
|
|
|
// Signature of key whose account provides the transaction fee
|
|
PayerSig [crypto.SECP256K1RSigLen]byte `serialize:"true"`
|
|
|
|
vm *VM
|
|
id ids.ID
|
|
bytes []byte
|
|
}
|
|
|
|
func (tx *CreateChainTx) initialize(vm *VM) error {
|
|
tx.vm = vm
|
|
txBytes, err := Codec.Marshal(tx) // byte repr. of the signed tx
|
|
tx.bytes = txBytes
|
|
tx.id = ids.NewID(hashing.ComputeHash256Array(txBytes))
|
|
return err
|
|
}
|
|
|
|
// ID of this transaction
|
|
func (tx *CreateChainTx) ID() ids.ID { return tx.id }
|
|
|
|
// Bytes returns the byte representation of a CreateChainTx
|
|
func (tx *CreateChainTx) Bytes() []byte { return tx.bytes }
|
|
|
|
// SyntacticVerify this transaction is well-formed
|
|
// Also populates [tx.Key] with the public key that signed this transaction
|
|
func (tx *CreateChainTx) SyntacticVerify() error {
|
|
switch {
|
|
case tx == nil:
|
|
return errNilTx
|
|
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():
|
|
return errInvalidID
|
|
case tx.VMID.IsZero():
|
|
return errInvalidVMID
|
|
case tx.SubnetID.Equals(DefaultSubnetID):
|
|
return errDSCantValidate
|
|
case !ids.IsSortedAndUniqueIDs(tx.FxIDs):
|
|
return errFxIDsNotSortedAndUnique
|
|
case !crypto.IsSortedAndUniqueSECP2561RSigs(tx.ControlSigs):
|
|
return errControlSigsNotSortedAndUnique
|
|
}
|
|
|
|
unsignedIntf := interface{}(&tx.UnsignedCreateChainTx)
|
|
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr of unsigned tx
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
payerKey, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.PayerSig[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tx.PayerAddress = payerKey.Address()
|
|
|
|
return nil
|
|
}
|
|
|
|
// SemanticVerify this transaction is valid.
|
|
func (tx *CreateChainTx) SemanticVerify(db database.Database) (func(), error) {
|
|
if err := tx.SyntacticVerify(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
currentChains, err := tx.vm.getChains(db) // chains that currently exist
|
|
if err != nil {
|
|
return nil, errDBChains
|
|
}
|
|
for _, chain := range currentChains {
|
|
if chain.ID().Equals(tx.ID()) {
|
|
return nil, fmt.Errorf("chain with ID %s already exists", chain.ID())
|
|
}
|
|
}
|
|
currentChains = append(currentChains, tx) // add this new chain
|
|
if err := tx.vm.putChains(db, currentChains); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Deduct tx fee from payer's account
|
|
account, err := tx.vm.getAccount(db, tx.PayerAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// txFee is removed in account.Remove
|
|
// TODO: Consider changing Remove to be parameterized on total amount (inc. tx fee) to remove
|
|
account, err = account.Remove(0, tx.Nonce)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := tx.vm.putAccount(db, account); err != nil {
|
|
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 and this node is a member of the
|
|
// subnet that validates the blockchain, create the blockchain
|
|
onAccept := func() {
|
|
tx.vm.createChain(tx)
|
|
}
|
|
|
|
return onAccept, nil
|
|
}
|
|
|
|
// We use this type so we can serialize a list of *CreateChainTx
|
|
// by defining a Bytes method on it
|
|
type createChainList []*CreateChainTx
|
|
|
|
// Bytes returns the byte representation of a list of *CreateChainTx
|
|
func (chains createChainList) Bytes() []byte {
|
|
bytes, _ := Codec.Marshal(chains)
|
|
return bytes
|
|
}
|
|
|
|
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,
|
|
FxIDs: fxIDs,
|
|
ChainName: chainName,
|
|
},
|
|
}
|
|
|
|
// Generate byte repr. of unsigned transaction
|
|
unsignedIntf := interface{}(&tx.UnsignedCreateChainTx)
|
|
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)
|
|
}
|
|
|
|
// 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.PayerSig[:], payerSig)
|
|
|
|
return tx, tx.initialize(vm)
|
|
}
|