mirror of https://github.com/poanetwork/gecko.git
createBlockchain takes genesis bytes rather than generating them. Add control signatures for blockchain creation. WIP not all tests pass
This commit is contained in:
parent
094651b38d
commit
c8ecea98b7
|
@ -17,6 +17,8 @@ import (
|
||||||
var (
|
var (
|
||||||
errInvalidVMID = errors.New("invalid VM ID")
|
errInvalidVMID = errors.New("invalid VM ID")
|
||||||
errFxIDsNotSortedAndUnique = errors.New("feature extensions IDs must be sorted and unique")
|
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
|
// UnsignedCreateChainTx is an unsigned CreateChainTx
|
||||||
|
@ -24,6 +26,9 @@ type UnsignedCreateChainTx struct {
|
||||||
// ID of the network this blockchain exists on
|
// ID of the network this blockchain exists on
|
||||||
NetworkID uint32 `serialize:"true"`
|
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.
|
// Next unused nonce of account paying the transaction fee for this transaction.
|
||||||
// Currently unused, as there are no tx fees.
|
// Currently unused, as there are no tx fees.
|
||||||
Nonce uint64 `serialize:"true"`
|
Nonce uint64 `serialize:"true"`
|
||||||
|
@ -37,7 +42,7 @@ type UnsignedCreateChainTx struct {
|
||||||
// IDs of the feature extensions running on the new chain
|
// IDs of the feature extensions running on the new chain
|
||||||
FxIDs []ids.ID `serialize:"true"`
|
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"`
|
GenesisData []byte `serialize:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,11 +50,19 @@ type UnsignedCreateChainTx struct {
|
||||||
type CreateChainTx struct {
|
type CreateChainTx struct {
|
||||||
UnsignedCreateChainTx `serialize:"true"`
|
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
|
vm *VM
|
||||||
id ids.ID
|
id ids.ID
|
||||||
key crypto.PublicKey // public key of transaction signer
|
|
||||||
bytes []byte
|
bytes []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,10 +77,6 @@ func (tx *CreateChainTx) initialize(vm *VM) error {
|
||||||
// ID of this transaction
|
// ID of this transaction
|
||||||
func (tx *CreateChainTx) ID() ids.ID { return tx.id }
|
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
|
// Bytes returns the byte representation of a CreateChainTx
|
||||||
func (tx *CreateChainTx) Bytes() []byte { return tx.bytes }
|
func (tx *CreateChainTx) Bytes() []byte { return tx.bytes }
|
||||||
|
|
||||||
|
@ -77,8 +86,8 @@ func (tx *CreateChainTx) SyntacticVerify() error {
|
||||||
switch {
|
switch {
|
||||||
case tx == nil:
|
case tx == nil:
|
||||||
return errNilTx
|
return errNilTx
|
||||||
case tx.key != nil:
|
case !tx.PayerAddress.IsZero(): // Only verify the transaction once
|
||||||
return nil // Only verify the transaction once
|
return nil
|
||||||
case tx.NetworkID != tx.vm.Ctx.NetworkID: // verify the transaction is on this network
|
case tx.NetworkID != tx.vm.Ctx.NetworkID: // verify the transaction is on this network
|
||||||
return errWrongNetworkID
|
return errWrongNetworkID
|
||||||
case tx.id.IsZero():
|
case tx.id.IsZero():
|
||||||
|
@ -87,6 +96,10 @@ func (tx *CreateChainTx) SyntacticVerify() error {
|
||||||
return errInvalidVMID
|
return errInvalidVMID
|
||||||
case !ids.IsSortedAndUniqueIDs(tx.FxIDs):
|
case !ids.IsSortedAndUniqueIDs(tx.FxIDs):
|
||||||
return errFxIDsNotSortedAndUnique
|
return errFxIDsNotSortedAndUnique
|
||||||
|
case tx.ControlSigs == nil:
|
||||||
|
return errControlSigsNil
|
||||||
|
case !crypto.IsSortedAndUniqueSECP2561RSigs(tx.ControlSigs):
|
||||||
|
return errControlSigsNotSortedAndUnique
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedIntf := interface{}(&tx.UnsignedCreateChainTx)
|
unsignedIntf := interface{}(&tx.UnsignedCreateChainTx)
|
||||||
|
@ -95,11 +108,11 @@ func (tx *CreateChainTx) SyntacticVerify() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:])
|
payerKey, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.PayerSig[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tx.key = key
|
tx.PayerAddress = payerKey.Address()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -125,11 +138,11 @@ func (tx *CreateChainTx) SemanticVerify(db database.Database) (func(), error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduct tx fee from payer's account
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
account, err = account.Remove(0, tx.Nonce)
|
account, err = account.Remove(txFee, tx.Nonce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -137,10 +150,56 @@ func (tx *CreateChainTx) SemanticVerify(db database.Database) (func(), error) {
|
||||||
return nil, err
|
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
|
// If this proposal is committed, create the new blockchain using the chain manager
|
||||||
onAccept := func() {
|
onAccept := func() {
|
||||||
chainParams := chains.ChainParameters{
|
chainParams := chains.ChainParameters{
|
||||||
ID: tx.ID(),
|
ID: tx.ID(),
|
||||||
|
SubnetID: tx.SubnetID,
|
||||||
GenesisData: tx.GenesisData,
|
GenesisData: tx.GenesisData,
|
||||||
VMAlias: tx.VMID.String(),
|
VMAlias: tx.VMID.String(),
|
||||||
}
|
}
|
||||||
|
@ -166,10 +225,14 @@ func (chains createChainList) Bytes() []byte {
|
||||||
return bytes
|
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{
|
tx := &CreateChainTx{
|
||||||
UnsignedCreateChainTx: UnsignedCreateChainTx{
|
UnsignedCreateChainTx: UnsignedCreateChainTx{
|
||||||
NetworkID: networkID,
|
NetworkID: networkID,
|
||||||
|
SubnetID: subnetID,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
GenesisData: genesisData,
|
GenesisData: genesisData,
|
||||||
VMID: vmID,
|
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)
|
unsignedIntf := interface{}(&tx.UnsignedCreateChainTx)
|
||||||
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction
|
unsignedBytes, err := Codec.Marshal(&unsignedIntf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
copy(tx.Sig[:], sig)
|
copy(tx.PayerSig[:], payerSig)
|
||||||
|
|
||||||
return tx, tx.initialize(vm)
|
return tx, tx.initialize(vm)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ package platformvm
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/database/versiondb"
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
"github.com/ava-labs/gecko/utils/crypto"
|
||||||
"github.com/ava-labs/gecko/vms/avm"
|
"github.com/ava-labs/gecko/vms/avm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,18 +24,19 @@ func TestCreateChainTxSyntacticVerify(t *testing.T) {
|
||||||
// Case 2: network ID is wrong
|
// Case 2: network ID is wrong
|
||||||
tx, err := vm.newCreateChainTx(
|
tx, err := vm.newCreateChainTx(
|
||||||
defaultNonce+1,
|
defaultNonce+1,
|
||||||
|
testSubnet1.ID,
|
||||||
nil,
|
nil,
|
||||||
avm.ID,
|
avm.ID,
|
||||||
nil,
|
nil,
|
||||||
"chain name",
|
"chain name",
|
||||||
testNetworkID+1,
|
testNetworkID+1,
|
||||||
|
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
|
||||||
defaultKey,
|
defaultKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = tx.SyntacticVerify()
|
err = tx.SyntacticVerify()
|
||||||
t.Log(err)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("should've errored because network ID is wrong")
|
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
|
// case 3: tx ID is empty
|
||||||
tx, err = vm.newCreateChainTx(
|
tx, err = vm.newCreateChainTx(
|
||||||
defaultNonce+1,
|
defaultNonce+1,
|
||||||
|
testSubnet1.ID,
|
||||||
nil,
|
nil,
|
||||||
avm.ID,
|
avm.ID,
|
||||||
nil,
|
nil,
|
||||||
"chain name",
|
"chain name",
|
||||||
testNetworkID,
|
testNetworkID,
|
||||||
|
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
|
||||||
defaultKey,
|
defaultKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -61,11 +64,13 @@ func TestCreateChainTxSyntacticVerify(t *testing.T) {
|
||||||
// Case 4: vm ID is empty
|
// Case 4: vm ID is empty
|
||||||
tx, err = vm.newCreateChainTx(
|
tx, err = vm.newCreateChainTx(
|
||||||
defaultNonce+1,
|
defaultNonce+1,
|
||||||
|
testSubnet1.ID,
|
||||||
nil,
|
nil,
|
||||||
avm.ID,
|
avm.ID,
|
||||||
nil,
|
nil,
|
||||||
"chain name",
|
"chain name",
|
||||||
testNetworkID,
|
testNetworkID,
|
||||||
|
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
|
||||||
defaultKey,
|
defaultKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -75,62 +80,209 @@ func TestCreateChainTxSyntacticVerify(t *testing.T) {
|
||||||
if err := tx.SyntacticVerify(); err == nil {
|
if err := tx.SyntacticVerify(); err == nil {
|
||||||
t.Fatal("should've errored because tx ID is empty")
|
t.Fatal("should've errored because tx ID is empty")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func TestSemanticVerify(t *testing.T) {
|
// Case 5: Control sigs not sorted
|
||||||
vm := defaultVM()
|
tx, err = vm.newCreateChainTx(
|
||||||
|
|
||||||
// create a tx
|
|
||||||
tx, err := vm.newCreateChainTx(
|
|
||||||
defaultNonce+1,
|
defaultNonce+1,
|
||||||
|
testSubnet1.ID,
|
||||||
nil,
|
nil,
|
||||||
avm.ID,
|
avm.ID,
|
||||||
nil,
|
nil,
|
||||||
"chain name",
|
"chain name",
|
||||||
testNetworkID,
|
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,
|
defaultKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newDB := versiondb.New(vm.DB)
|
_, err = tx.SemanticVerify(vm.DB)
|
||||||
|
if err == nil {
|
||||||
_, err = tx.SemanticVerify(newDB)
|
t.Fatal("should have errored because there are no control sigs")
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chains, err := vm.getChains(newDB)
|
// Case 2: 1 control sig (2 are needed)
|
||||||
if err != nil {
|
tx, err = vm.newCreateChainTx(
|
||||||
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(
|
|
||||||
defaultNonce+1,
|
defaultNonce+1,
|
||||||
|
testSubnet1.ID,
|
||||||
nil,
|
nil,
|
||||||
avm.ID,
|
avm.ID,
|
||||||
nil,
|
nil,
|
||||||
"chain name",
|
"chain name",
|
||||||
testNetworkID,
|
testNetworkID,
|
||||||
|
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0]},
|
||||||
defaultKey,
|
defaultKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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 {
|
if err := vm.putChains(vm.DB, []*CreateChainTx{tx}); err != nil {
|
||||||
t.Fatal(err)
|
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)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
|
||||||
|
|
||||||
"github.com/gorilla/rpc/v2/json2"
|
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/database"
|
"github.com/ava-labs/gecko/database"
|
||||||
"github.com/ava-labs/gecko/ids"
|
"github.com/ava-labs/gecko/ids"
|
||||||
|
@ -32,22 +29,6 @@ var (
|
||||||
errGetStakeSource = errors.New("couldn't get account specified in '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
|
// Service defines the API calls that can be made to the platform chain
|
||||||
type Service struct{ vm *VM }
|
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)
|
genTx.Tx, err = service.signAddNonDefaultSubnetValidatorTx(tx, key)
|
||||||
case *CreateSubnetTx:
|
case *CreateSubnetTx:
|
||||||
genTx.Tx, err = service.signCreateSubnetTx(tx, key)
|
genTx.Tx, err = service.signCreateSubnetTx(tx, key)
|
||||||
|
case *CreateChainTx:
|
||||||
|
genTx.Tx, err = service.signCreateChainTx(tx, key)
|
||||||
default:
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -773,6 +756,59 @@ func (service *Service) signAddNonDefaultSubnetValidatorTx(tx *addNonDefaultSubn
|
||||||
return tx, nil
|
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
|
// IssueTxArgs are the arguments to IssueTx
|
||||||
type IssueTxArgs struct {
|
type IssueTxArgs struct {
|
||||||
// Tx being sent to the network
|
// Tx being sent to the network
|
||||||
|
@ -860,7 +896,6 @@ func (service *Service) CreateSubnet(_ *http.Request, args *CreateSubnetArgs, re
|
||||||
|
|
||||||
response.UnsignedTx.Bytes = txBytes
|
response.UnsignedTx.Bytes = txBytes
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -871,6 +906,9 @@ func (service *Service) CreateSubnet(_ *http.Request, args *CreateSubnetArgs, re
|
||||||
|
|
||||||
// CreateBlockchainArgs is the arguments for calling CreateBlockchain
|
// CreateBlockchainArgs is the arguments for calling CreateBlockchain
|
||||||
type CreateBlockchainArgs struct {
|
type CreateBlockchainArgs struct {
|
||||||
|
// ID of Subnet that validates the new blockchain
|
||||||
|
SubnetID ids.ID
|
||||||
|
|
||||||
// ID of the VM the new blockchain is running
|
// ID of the VM the new blockchain is running
|
||||||
VMID string `json:"vmID"`
|
VMID string `json:"vmID"`
|
||||||
|
|
||||||
|
@ -880,26 +918,21 @@ type CreateBlockchainArgs struct {
|
||||||
// Human-readable name for the new blockchain, not necessarily unique
|
// Human-readable name for the new blockchain, not necessarily unique
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// To generate the byte representation of the genesis data for this blockchain,
|
// Next unused nonce of the account paying the transaction fee
|
||||||
// a POST request with body [GenesisData] is made to the API method whose name is [Method], whose
|
PayerNonce json.Uint64 `json:"payerNonce"`
|
||||||
// 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
|
// Genesis state of the blockchain being created
|
||||||
type CreateGenesisReply struct {
|
GenesisData formatting.CB58 `json:"genesisData"`
|
||||||
Bytes formatting.CB58 `json:"bytes"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBlockchainReply is the reply from calling CreateBlockchain
|
// CreateBlockchainReply is the reply from calling CreateBlockchain
|
||||||
type CreateBlockchainReply struct {
|
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
|
// CreateBlockchain returns an unsigned transaction to create a new blockchain
|
||||||
func (service *Service) CreateBlockchain(_ *http.Request, args *CreateBlockchainArgs, reply *CreateBlockchainReply) error {
|
// 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)
|
vmID, err := service.vm.ChainManager.LookupVM(args.VMID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("no VM with ID '%s' found", args.VMID)
|
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)
|
fxIDs = append(fxIDs, fxID)
|
||||||
}
|
}
|
||||||
|
|
||||||
genesisBytes := []byte(nil)
|
tx := CreateChainTx{
|
||||||
if args.Method != "" {
|
UnsignedCreateChainTx: UnsignedCreateChainTx{
|
||||||
buf, err := json2.EncodeClientRequest(args.Method, args.GenesisData)
|
NetworkID: service.vm.Ctx.NetworkID,
|
||||||
if err != nil {
|
SubnetID: args.SubnetID,
|
||||||
return fmt.Errorf("problem building blockchain genesis state: %w", err)
|
Nonce: uint64(args.PayerNonce),
|
||||||
}
|
ChainName: args.Name,
|
||||||
|
VMID: vmID,
|
||||||
writer := httptest.NewRecorder()
|
FxIDs: fxIDs,
|
||||||
service.vm.Ctx.HTTP.Call(
|
GenesisData: args.GenesisData.Bytes,
|
||||||
/*writer=*/ writer,
|
|
||||||
/*method=*/ "POST",
|
|
||||||
/*base=*/ args.VMID,
|
|
||||||
/*endpoint=*/ args.Endpoint,
|
|
||||||
/*body=*/ bytes.NewBuffer(buf),
|
|
||||||
/*headers=*/ map[string]string{
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
},
|
||||||
)
|
PayerAddress: ids.ShortID{},
|
||||||
|
PayerSig: [crypto.SECP256K1RSigLen]byte{},
|
||||||
result := CreateGenesisReply{}
|
ControlSigs: nil,
|
||||||
if err := json2.DecodeClientResponse(writer.Body, &result); err != nil {
|
vm: nil,
|
||||||
return fmt.Errorf("problem building blockchain genesis state: %w", err)
|
id: ids.ID{},
|
||||||
}
|
bytes: nil,
|
||||||
genesisBytes = result.Bytes.Bytes
|
|
||||||
} else if args.GenesisData != nil {
|
|
||||||
return errNoMethodWithGenesis
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should use the key store to sign this transaction.
|
txBytes, err := Codec.Marshal(genericTx{Tx: &tx})
|
||||||
// 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("problem creating transaction: %w", err)
|
return errCreatingTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this tx to the set of unissued txs
|
response.UnsignedTx.Bytes = txBytes
|
||||||
service.vm.unissuedDecisionTxs = append(service.vm.unissuedDecisionTxs, tx)
|
|
||||||
service.vm.resetTimer()
|
|
||||||
|
|
||||||
reply.BlockchainID = tx.ID()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/ava-labs/gecko/ids"
|
"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/formatting"
|
||||||
"github.com/ava-labs/gecko/utils/json"
|
"github.com/ava-labs/gecko/utils/json"
|
||||||
)
|
)
|
||||||
|
@ -74,11 +75,13 @@ type APIDefaultSubnetValidator struct {
|
||||||
// [VMID] is the ID of the VM this chain runs.
|
// [VMID] is the ID of the VM this chain runs.
|
||||||
// [FxIDs] are the IDs of the Fxs the chain supports.
|
// [FxIDs] are the IDs of the Fxs the chain supports.
|
||||||
// [Name] is a human-readable, non-unique name for the chain.
|
// [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 {
|
type APIChain struct {
|
||||||
GenesisData formatting.CB58 `json:"genesisData"`
|
GenesisData formatting.CB58 `json:"genesisData"`
|
||||||
VMID ids.ID `json:"vmID"`
|
VMID ids.ID `json:"vmID"`
|
||||||
FxIDs []ids.ID `json:"fxIDs"`
|
FxIDs []ids.ID `json:"fxIDs"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
SubnetID ids.ID `json:"subnetID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildGenesisArgs are the arguments used to create
|
// BuildGenesisArgs are the arguments used to create
|
||||||
|
@ -182,12 +185,15 @@ func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, repl
|
||||||
tx := &CreateChainTx{
|
tx := &CreateChainTx{
|
||||||
UnsignedCreateChainTx: UnsignedCreateChainTx{
|
UnsignedCreateChainTx: UnsignedCreateChainTx{
|
||||||
NetworkID: uint32(args.NetworkID),
|
NetworkID: uint32(args.NetworkID),
|
||||||
|
SubnetID: chain.SubnetID,
|
||||||
Nonce: 0,
|
Nonce: 0,
|
||||||
ChainName: chain.Name,
|
ChainName: chain.Name,
|
||||||
VMID: chain.VMID,
|
VMID: chain.VMID,
|
||||||
FxIDs: chain.FxIDs,
|
FxIDs: chain.FxIDs,
|
||||||
GenesisData: chain.GenesisData.Bytes,
|
GenesisData: chain.GenesisData.Bytes,
|
||||||
},
|
},
|
||||||
|
ControlSigs: [][crypto.SECP256K1RSigLen]byte{},
|
||||||
|
PayerSig: [crypto.SECP256K1RSigLen]byte{},
|
||||||
}
|
}
|
||||||
if err := tx.initialize(nil); err != nil {
|
if err := tx.initialize(nil); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -81,6 +81,7 @@ func TestBuildGenesis(t *testing.T) {
|
||||||
Destination: addr,
|
Destination: addr,
|
||||||
}
|
}
|
||||||
chains := APIChain{
|
chains := APIChain{
|
||||||
|
SubnetID: DefaultSubnetID,
|
||||||
GenesisData: genesisData,
|
GenesisData: genesisData,
|
||||||
VMID: vmID,
|
VMID: vmID,
|
||||||
Name: "My Favorite Episode",
|
Name: "My Favorite Episode",
|
||||||
|
|
|
@ -35,16 +35,17 @@ var (
|
||||||
// each key corresponds to an account that has $AVA and a genesis validator
|
// each key corresponds to an account that has $AVA and a genesis validator
|
||||||
keys []*crypto.PrivateKeySECP256K1R
|
keys []*crypto.PrivateKeySECP256K1R
|
||||||
|
|
||||||
// amount all genesis validators stake
|
// amount all genesis validators stake in defaultVM
|
||||||
defaultStakeAmount uint64
|
defaultStakeAmount uint64
|
||||||
|
|
||||||
// balance of accounts that exist at genesis
|
// balance of accounts that exist at genesis in defaultVM
|
||||||
defaultBalance = 100 * MinimumStakeAmount
|
defaultBalance = 100 * MinimumStakeAmount
|
||||||
|
|
||||||
// At genesis this account has AVA and is validating the default subnet
|
// At genesis this account has AVA and is validating the default subnet
|
||||||
defaultKey *crypto.PrivateKeySECP256K1R
|
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
|
testSubnet1 *CreateSubnetTx
|
||||||
testSubnet1ControlKeys []*crypto.PrivateKeySECP256K1R
|
testSubnet1ControlKeys []*crypto.PrivateKeySECP256K1R
|
||||||
)
|
)
|
||||||
|
@ -132,7 +133,7 @@ func defaultVM() *VM {
|
||||||
testNetworkID,
|
testNetworkID,
|
||||||
0,
|
0,
|
||||||
[]ids.ShortID{keys[0].PublicKey().Address(), keys[1].PublicKey().Address(), keys[2].PublicKey().Address()}, // control keys are keys[0], keys[1], keys[2]
|
[]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],
|
keys[0],
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -761,11 +762,13 @@ func TestCreateChain(t *testing.T) {
|
||||||
|
|
||||||
tx, err := vm.newCreateChainTx(
|
tx, err := vm.newCreateChainTx(
|
||||||
defaultNonce+1,
|
defaultNonce+1,
|
||||||
|
testSubnet1.ID,
|
||||||
nil,
|
nil,
|
||||||
timestampvm.ID,
|
timestampvm.ID,
|
||||||
nil,
|
nil,
|
||||||
"name",
|
"name",
|
||||||
testNetworkID,
|
testNetworkID,
|
||||||
|
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
|
||||||
keys[0],
|
keys[0],
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -802,7 +805,7 @@ func TestCreateChain(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify tx fee was deducted
|
// 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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue