diff --git a/snow/engine/avalanche/tx_job.go b/snow/engine/avalanche/tx_job.go index 0462bd3..f0ffe70 100644 --- a/snow/engine/avalanche/tx_job.go +++ b/snow/engine/avalanche/tx_job.go @@ -54,12 +54,9 @@ func (t *txJob) Execute() { case choices.Unknown, choices.Rejected: t.numDropped.Inc() case choices.Processing: - if err := t.tx.Verify(); err == nil { - t.tx.Accept() - t.numAccepted.Inc() - } else { - t.numDropped.Inc() - } + t.tx.Verify() + t.tx.Accept() + t.numAccepted.Inc() } } func (t *txJob) Bytes() []byte { return t.tx.Bytes() } diff --git a/snow/engine/snowman/block_job.go b/snow/engine/snowman/block_job.go index aab227f..ec5f4a3 100644 --- a/snow/engine/snowman/block_job.go +++ b/snow/engine/snowman/block_job.go @@ -51,12 +51,9 @@ func (b *blockJob) Execute() { case choices.Unknown, choices.Rejected: b.numDropped.Inc() case choices.Processing: - if err := b.blk.Verify(); err == nil { - b.blk.Accept() - b.numAccepted.Inc() - } else { - b.numDropped.Inc() - } + b.blk.Verify() + b.blk.Accept() + b.numAccepted.Inc() } } func (b *blockJob) Bytes() []byte { return b.blk.Bytes() } diff --git a/vms/avm/base_tx.go b/vms/avm/base_tx.go index 0926b9e..a0d2cc4 100644 --- a/vms/avm/base_tx.go +++ b/vms/avm/base_tx.go @@ -37,20 +37,6 @@ type BaseTx struct { Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction } -// NetworkID is the ID of the network on which this transaction exists -func (t *BaseTx) NetworkID() uint32 { return t.NetID } - -// ChainID is the ID of the chain on which this transaction exists -func (t *BaseTx) ChainID() ids.ID { return t.BCID } - -// Outputs track which outputs this transaction is producing. The returned array -// should not be modified. -func (t *BaseTx) Outputs() []*ava.TransferableOutput { return t.Outs } - -// Inputs track which UTXOs this transaction is consuming. The returned array -// should not be modified. -func (t *BaseTx) Inputs() []*ava.TransferableInput { return t.Ins } - // InputUTXOs track which UTXOs this transaction is consuming. func (t *BaseTx) InputUTXOs() []*ava.UTXOID { utxos := []*ava.UTXOID(nil) diff --git a/vms/avm/base_tx_test.go b/vms/avm/base_tx_test.go index 7b0f7a7..3236e16 100644 --- a/vms/avm/base_tx_test.go +++ b/vms/avm/base_tx_test.go @@ -167,19 +167,7 @@ func TestBaseTxGetters(t *testing.T) { txID := tx.ID() - if netID := tx.NetworkID(); netID != networkID { - t.Fatalf("Wrong network ID returned") - } else if bcID := tx.ChainID(); !bcID.Equals(chainID) { - t.Fatalf("Wrong chain ID returned") - } else if outs := tx.Outputs(); len(outs) != 1 { - t.Fatalf("Outputs returned wrong number of outs") - } else if out := outs[0]; out != tx.Outs[0] { - t.Fatalf("Outputs returned wrong output") - } else if ins := tx.Inputs(); len(ins) != 1 { - t.Fatalf("Inputs returned wrong number of ins") - } else if in := ins[0]; in != tx.Ins[0] { - t.Fatalf("Inputs returned wrong input") - } else if assets := tx.AssetIDs(); assets.Len() != 1 { + if assets := tx.AssetIDs(); assets.Len() != 1 { t.Fatalf("Wrong number of assets returned") } else if !assets.Contains(asset) { t.Fatalf("Wrong asset returned") @@ -191,8 +179,6 @@ func TestBaseTxGetters(t *testing.T) { t.Fatalf("Wrong output index returned") } else if assetID := utxo.AssetID(); !assetID.Equals(asset) { t.Fatalf("Wrong asset ID returned") - } else if utxoOut := utxo.Out; utxoOut != out.Out { - t.Fatalf("Wrong output returned") } } diff --git a/vms/avm/export_tx.go b/vms/avm/export_tx.go index 22fef13..31f6860 100644 --- a/vms/avm/export_tx.go +++ b/vms/avm/export_tx.go @@ -17,7 +17,7 @@ import ( type ExportTx struct { BaseTx `serialize:"true"` - ExportOuts []*ava.TransferableOutput `serialize:"true"` // The outputs this transaction is sending to the other chain + Outs []*ava.TransferableOutput `serialize:"true"` // The outputs this transaction is sending to the other chain } // SyntacticVerify that this transaction is well-formed. @@ -32,6 +32,16 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) erro } fc := ava.NewFlowChecker() + for _, out := range t.BaseTx.Outs { + if err := out.Verify(); err != nil { + return err + } + fc.Produce(out.AssetID(), out.Output().Amount()) + } + if !ava.IsSortedTransferableOutputs(t.BaseTx.Outs, c) { + return errOutputsNotSorted + } + for _, out := range t.Outs { if err := out.Verify(); err != nil { return err @@ -42,16 +52,6 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) erro return errOutputsNotSorted } - for _, out := range t.ExportOuts { - if err := out.Verify(); err != nil { - return err - } - fc.Produce(out.AssetID(), out.Output().Amount()) - } - if !ava.IsSortedTransferableOutputs(t.ExportOuts, c) { - return errOutputsNotSorted - } - for _, in := range t.Ins { if err := in.Verify(); err != nil { return err @@ -102,7 +102,7 @@ func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab } } - for _, out := range t.ExportOuts { + for _, out := range t.Outs { if !out.AssetID().Equals(vm.ava) { return errWrongAssetID } @@ -121,11 +121,11 @@ func (t *ExportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { vsmDB := versiondb.New(smDB) state := ava.NewPrefixedState(vsmDB, vm.codec) - for i, out := range t.ExportOuts { + for i, out := range t.Outs { utxo := &ava.UTXO{ UTXOID: ava.UTXOID{ TxID: txID, - OutputIndex: uint32(len(t.Outs) + i), + OutputIndex: uint32(len(t.BaseTx.Outs) + i), }, Asset: ava.Asset{ID: out.AssetID()}, Out: out.Out, diff --git a/vms/avm/export_tx_test.go b/vms/avm/export_tx_test.go index a2af503..e2d75a0 100644 --- a/vms/avm/export_tx_test.go +++ b/vms/avm/export_tx_test.go @@ -168,7 +168,7 @@ func TestIssueExportTx(t *testing.T) { }, }}, }, - ExportOuts: []*ava.TransferableOutput{&ava.TransferableOutput{ + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, @@ -301,7 +301,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) { }, }}, }, - ExportOuts: []*ava.TransferableOutput{&ava.TransferableOutput{ + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, diff --git a/vms/avm/import_tx.go b/vms/avm/import_tx.go index 41dc701..d47542c 100644 --- a/vms/avm/import_tx.go +++ b/vms/avm/import_tx.go @@ -20,8 +20,7 @@ import ( type ImportTx struct { BaseTx `serialize:"true"` - Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction - Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction + Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction } // InputUTXOs track which UTXOs this transaction is consuming. @@ -43,25 +42,6 @@ func (t *ImportTx) AssetIDs() ids.Set { return assets } -// UTXOs returns the UTXOs transaction is producing. -func (t *ImportTx) UTXOs() []*ava.UTXO { - txID := t.ID() - utxos := t.BaseTx.UTXOs() - - for _, out := range t.Outs { - utxos = append(utxos, &ava.UTXO{ - UTXOID: ava.UTXOID{ - TxID: txID, - OutputIndex: uint32(len(utxos)), - }, - Asset: ava.Asset{ID: out.AssetID()}, - Out: out.Out, - }) - } - - return utxos -} - var ( errNoImportInputs = errors.New("no import inputs") ) @@ -71,14 +51,14 @@ func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) switch { case t == nil: return errNilTx + case t.NetID != ctx.NetworkID: + return errWrongNetworkID + case !t.BCID.Equals(ctx.ChainID): + return errWrongChainID case len(t.Ins) == 0: return errNoImportInputs } - if err := t.BaseTx.SyntacticVerify(ctx, c, numFxs); err != nil { - return err - } - fc := ava.NewFlowChecker() for _, out := range t.Outs { if err := out.Verify(); err != nil { @@ -90,6 +70,16 @@ func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) return errOutputsNotSorted } + for _, in := range t.BaseTx.Ins { + if err := in.Verify(); err != nil { + return err + } + fc.Consume(in.AssetID(), in.Input().Amount()) + } + if !ava.IsSortedAndUniqueTransferableInputs(t.BaseTx.Ins) { + return errInputsNotSortedUnique + } + for _, in := range t.Ins { if err := in.Verify(); err != nil { return err diff --git a/vms/avm/import_tx_test.go b/vms/avm/import_tx_test.go index 89b5dff..88fad56 100644 --- a/vms/avm/import_tx_test.go +++ b/vms/avm/import_tx_test.go @@ -34,8 +34,6 @@ func TestImportTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, // number of base inputs: 0x00, 0x00, 0x00, 0x00, - // number of outs: - 0x00, 0x00, 0x00, 0x00, // number of inputs: 0x00, 0x00, 0x00, 0x01, // utxoID: diff --git a/vms/avm/service.go b/vms/avm/service.go index a2a6bb3..9e80b89 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -948,6 +948,147 @@ func (service *Service) SignMintTx(r *http.Request, args *SignMintTxArgs, reply return nil } +// SendImportArgs are arguments for passing into SendImport requests +type SendImportArgs struct { + Username string `json:"username"` + Password string `json:"password"` + To string `json:"to"` +} + +// SendImportReply defines the SendImport replies returned from the API +type SendImportReply struct { + TxID ids.ID `json:"txID"` +} + +// SendImport returns the ID of the newly created atomic transaction +func (service *Service) SendImport(_ *http.Request, args *SendImportArgs, reply *SendImportReply) error { + service.vm.ctx.Log.Verbo("SendExport called with username: %s", args.Username) + + toBytes, err := service.vm.Parse(args.To) + if err != nil { + return fmt.Errorf("problem parsing to address: %w", err) + } + to, err := ids.ToShortID(toBytes) + if err != nil { + return fmt.Errorf("problem parsing to address: %w", err) + } + + db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) + if err != nil { + return fmt.Errorf("problem retrieving user: %w", err) + } + + user := userState{vm: service.vm} + + addresses, _ := user.Addresses(db) + + addrs := ids.Set{} + addrs.Add(addresses...) + utxos, err := service.vm.GetAtomicUTXOs(addrs) + if err != nil { + return fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err) + } + + kc := secp256k1fx.NewKeychain() + for _, addr := range addresses { + sk, err := user.Key(db, addr) + if err != nil { + return fmt.Errorf("problem retrieving private key: %w", err) + } + kc.Add(sk) + } + + amount := uint64(0) + time := service.vm.clock.Unix() + + ins := []*ava.TransferableInput{} + keys := [][]*crypto.PrivateKeySECP256K1R{} + for _, utxo := range utxos { + if !utxo.AssetID().Equals(service.vm.ava) { + continue + } + inputIntf, signers, err := kc.Spend(utxo.Out, time) + if err != nil { + continue + } + input, ok := inputIntf.(ava.Transferable) + if !ok { + continue + } + spent, err := math.Add64(amount, input.Amount()) + if err != nil { + return errSpendOverflow + } + amount = spent + + in := &ava.TransferableInput{ + UTXOID: utxo.UTXOID, + Asset: ava.Asset{ID: service.vm.ava}, + In: input, + } + + ins = append(ins, in) + keys = append(keys, signers) + } + + ava.SortTransferableInputsWithSigners(ins, keys) + + outs := []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: service.vm.ava}, + Out: &secp256k1fx.TransferOutput{ + Amt: amount, + Locktime: 0, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{to}, + }, + }, + }} + + tx := Tx{UnsignedTx: &ImportTx{ + BaseTx: BaseTx{ + NetID: service.vm.ctx.NetworkID, + BCID: service.vm.ctx.ChainID, + Outs: outs, + }, + Ins: ins, + }} + + unsignedBytes, err := service.vm.codec.Marshal(&tx.UnsignedTx) + if err != nil { + return fmt.Errorf("problem creating transaction: %w", err) + } + hash := hashing.ComputeHash256(unsignedBytes) + + for _, credKeys := range keys { + cred := &secp256k1fx.Credential{} + for _, key := range credKeys { + sig, err := key.SignHash(hash) + if err != nil { + return fmt.Errorf("problem creating transaction: %w", err) + } + fixedSig := [crypto.SECP256K1RSigLen]byte{} + copy(fixedSig[:], sig) + + cred.Sigs = append(cred.Sigs, fixedSig) + } + tx.Creds = append(tx.Creds, cred) + } + + b, err := service.vm.codec.Marshal(tx) + if err != nil { + return fmt.Errorf("problem creating transaction: %w", err) + } + + txID, err := service.vm.IssueTx(b, nil) + if err != nil { + return fmt.Errorf("problem issuing transaction: %w", err) + } + + reply.TxID = txID + return nil +} + // SendExportArgs are arguments for passing into SendExport requests type SendExportArgs struct { Username string `json:"username"` @@ -1083,7 +1224,7 @@ func (service *Service) SendExport(_ *http.Request, args *SendExportArgs, reply Outs: outs, Ins: ins, }, - ExportOuts: exportOuts, + Outs: exportOuts, }} unsignedBytes, err := service.vm.codec.Marshal(&tx.UnsignedTx) diff --git a/vms/avm/tx.go b/vms/avm/tx.go index 365d37c..fcbeddc 100644 --- a/vms/avm/tx.go +++ b/vms/avm/tx.go @@ -24,11 +24,6 @@ type UnsignedTx interface { ID() ids.ID Bytes() []byte - NetworkID() uint32 - ChainID() ids.ID - Outputs() []*ava.TransferableOutput - Inputs() []*ava.TransferableInput - AssetIDs() ids.Set InputUTXOs() []*ava.UTXOID UTXOs() []*ava.UTXO diff --git a/vms/avm/vm.go b/vms/avm/vm.go index 543408c..909c4a5 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -275,6 +275,31 @@ func (vm *VM) IssueTx(b []byte, onDecide func(choices.Status)) (ids.ID, error) { return tx.ID(), nil } +// GetAtomicUTXOs returns the utxos that at least one of the provided addresses is +// referenced in. +func (vm *VM) GetAtomicUTXOs(addrs ids.Set) ([]*ava.UTXO, error) { + smDB := vm.ctx.SharedMemory.GetDatabase(vm.platform) + defer vm.ctx.SharedMemory.ReleaseDatabase(vm.platform) + + state := ava.NewPrefixedState(smDB, vm.codec) + + utxoIDs := ids.Set{} + for _, addr := range addrs.List() { + utxos, _ := state.PlatformFunds(addr) + utxoIDs.Add(utxos...) + } + + utxos := []*ava.UTXO{} + for _, utxoID := range utxoIDs.List() { + utxo, err := state.PlatformUTXO(utxoID) + if err != nil { + return nil, err + } + utxos = append(utxos, utxo) + } + return utxos, nil +} + // GetUTXOs returns the utxos that at least one of the provided addresses is // referenced in. func (vm *VM) GetUTXOs(addrs ids.Set) ([]*ava.UTXO, error) { diff --git a/vms/platformvm/add_nondefault_subnet_validator_tx.go b/vms/platformvm/add_nondefault_subnet_validator_tx.go index 6173950..531570a 100644 --- a/vms/platformvm/add_nondefault_subnet_validator_tx.go +++ b/vms/platformvm/add_nondefault_subnet_validator_tx.go @@ -162,7 +162,7 @@ func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) ( } var subnet *CreateSubnetTx for _, sn := range subnets { - if sn.ID.Equals(tx.SubnetID()) { + if sn.id.Equals(tx.SubnetID()) { subnet = sn break } diff --git a/vms/platformvm/add_nondefault_subnet_validator_tx_test.go b/vms/platformvm/add_nondefault_subnet_validator_tx_test.go index 2d63f06..c29faf1 100644 --- a/vms/platformvm/add_nondefault_subnet_validator_tx_test.go +++ b/vms/platformvm/add_nondefault_subnet_validator_tx_test.go @@ -28,7 +28,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -48,7 +48,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID+1, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -67,7 +67,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -87,7 +87,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -107,7 +107,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -126,7 +126,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix())-1, defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -147,7 +147,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateStartTime.Add(MinimumStakingDuration).Unix())-1, defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -167,7 +167,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateStartTime.Add(MaximumStakingDuration).Unix())+1, defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -187,7 +187,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -212,7 +212,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix())+1, defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -235,7 +235,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -245,7 +245,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { } _, _, _, _, err = tx.SemanticVerify(vm.DB) if err != nil { - t.Log(testSubnet1.ID) + t.Log(testSubnet1.id) subnets, err := vm.getSubnets(vm.DB) if err != nil { t.Fatal(err) @@ -253,7 +253,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { if len(subnets) == 0 { t.Fatal("no subnets found") } - t.Logf("subnets[0].ID: %v", subnets[0].ID) + t.Logf("subnets[0].ID: %v", subnets[0].id) t.Fatal(err) } @@ -290,7 +290,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(DSStartTime.Unix()), // start validating non-default subnet before default subnet uint64(DSEndTime.Unix()), pendingDSValidatorID, - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -324,7 +324,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(DSStartTime.Unix())-1, // start validating non-default subnet before default subnet uint64(DSEndTime.Unix()), pendingDSValidatorID, - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -346,7 +346,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(DSStartTime.Unix()), uint64(DSEndTime.Unix())+1, // stop validating non-default subnet after stopping validating default subnet pendingDSValidatorID, - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -368,7 +368,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(DSStartTime.Unix()), // same start time as for default subnet uint64(DSEndTime.Unix()), // same end time as for default subnet pendingDSValidatorID, - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -389,12 +389,12 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { } tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(newTimestamp.Unix()), // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(newTimestamp.Unix()), // start time uint64(newTimestamp.Add(MinimumStakingDuration).Unix()), // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, // tx fee payer @@ -429,7 +429,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), // start time uint64(defaultValidateEndTime.Unix()), // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, newAcctKey.(*crypto.PrivateKeySECP256K1R), // tx fee payer @@ -451,7 +451,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), // start time uint64(defaultValidateEndTime.Unix()), // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, // tx fee payer @@ -465,7 +465,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { SortByStartTime: false, Txs: []TimedTx{tx}, }, - testSubnet1.ID, + testSubnet1.id, ) // Node with ID nodeIDKey.PublicKey().Address() now validating subnet with ID testSubnet1.ID @@ -475,7 +475,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), // start time uint64(defaultValidateEndTime.Unix()), // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, // tx fee payer @@ -494,17 +494,17 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { &EventHeap{ SortByStartTime: false, }, - testSubnet1.ID, + testSubnet1.id, ) // Case 9: Too many signatures tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(defaultGenesisTime.Unix()), // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(defaultGenesisTime.Unix()), // start time uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix())+1, // end time keys[0].PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1], testSubnet1ControlKeys[2]}, defaultKey, // tx fee payer @@ -520,12 +520,12 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { // Case 10: Too few signatures tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(defaultGenesisTime.Unix()), // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(defaultGenesisTime.Unix()), // start time uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix()), // end time keys[0].PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[2]}, defaultKey, // tx fee payer @@ -541,12 +541,12 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { // Case 10: Control Signature from invalid key tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(defaultGenesisTime.Unix()), // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(defaultGenesisTime.Unix()), // start time uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix()), // end time keys[0].PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], keys[3]}, defaultKey, // tx fee payer @@ -563,12 +563,12 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { // Case 11: Proposed validator in pending validator set for subnet // First, add validator to pending validator set of subnet tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(defaultGenesisTime.Unix())+1, // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(defaultGenesisTime.Unix())+1, // start time uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix())+1, // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, // tx fee payer @@ -582,7 +582,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { SortByStartTime: true, Txs: []TimedTx{tx}, }, - testSubnet1.ID, + testSubnet1.id, ) // Node with ID nodeIDKey.PublicKey().Address() now pending validator for subnet with ID testSubnet1.ID @@ -604,7 +604,7 @@ func TestAddNonDefaultSubnetValidatorMarshal(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, diff --git a/vms/platformvm/advance_time_tx.go b/vms/platformvm/advance_time_tx.go index b126ec8..17bca78 100644 --- a/vms/platformvm/advance_time_tx.go +++ b/vms/platformvm/advance_time_tx.go @@ -105,15 +105,15 @@ func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Databa return nil, nil, nil, nil, err } for _, subnet := range subnets { - current, pending, err := tx.vm.calculateValidators(db, tx.Timestamp(), subnet.ID) + current, pending, err := tx.vm.calculateValidators(db, tx.Timestamp(), subnet.id) if err != nil { return nil, nil, nil, nil, err } - if err := tx.vm.putCurrentValidators(onCommitDB, current, subnet.ID); err != nil { + if err := tx.vm.putCurrentValidators(onCommitDB, current, subnet.id); err != nil { return nil, nil, nil, nil, err } - if err := tx.vm.putPendingValidators(onCommitDB, pending, subnet.ID); err != nil { + if err := tx.vm.putPendingValidators(onCommitDB, pending, subnet.id); err != nil { return nil, nil, nil, nil, err } } @@ -127,7 +127,7 @@ func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Databa return } for _, subnet := range subnets { - if err := tx.vm.updateValidators(subnet.ID); err != nil { + if err := tx.vm.updateValidators(subnet.id); err != nil { tx.vm.Ctx.Log.Debug("failed to update validators on the default subnet: %s", err) } } diff --git a/vms/platformvm/atomic_block.go b/vms/platformvm/atomic_block.go index 4af5ff7..8b973ad 100644 --- a/vms/platformvm/atomic_block.go +++ b/vms/platformvm/atomic_block.go @@ -21,6 +21,8 @@ var ( type AtomicTx interface { initialize(vm *VM) error + ID() ids.ID + // UTXOs this tx consumes InputUTXOs() ids.Set diff --git a/vms/platformvm/create_subnet_tx.go b/vms/platformvm/create_subnet_tx.go index 0d33ca7..f61e157 100644 --- a/vms/platformvm/create_subnet_tx.go +++ b/vms/platformvm/create_subnet_tx.go @@ -23,12 +23,6 @@ var ( // UnsignedCreateSubnetTx is an unsigned proposal to create a new subnet type UnsignedCreateSubnetTx struct { - // The VM this tx exists within - vm *VM - - // ID is this transaction's ID - ID ids.ID - // NetworkID is the ID of the network this tx was issued on NetworkID uint32 `serialize:"true"` @@ -47,6 +41,12 @@ type UnsignedCreateSubnetTx struct { type CreateSubnetTx struct { UnsignedCreateSubnetTx `serialize:"true"` + // The VM this tx exists within + vm *VM + + // ID is this transaction's ID + id ids.ID + // The public key that signed this transaction // The transaction fee will be paid from the corresponding account // (ie the account whose ID is [key].Address()) @@ -60,6 +60,9 @@ type CreateSubnetTx struct { bytes []byte } +// ID returns the ID of this tx +func (tx *CreateSubnetTx) ID() ids.ID { return tx.id } + // SyntacticVerify nil iff [tx] is syntactically valid. // If [tx] is valid, this method sets [tx.key] func (tx *CreateSubnetTx) SyntacticVerify() error { @@ -68,7 +71,7 @@ func (tx *CreateSubnetTx) SyntacticVerify() error { return errNilTx case tx.key != nil: return nil // Only verify the transaction once - case tx.ID.IsZero(): + case tx.id.IsZero(): return errInvalidID case tx.NetworkID != tx.vm.Ctx.NetworkID: return errWrongNetworkID @@ -106,8 +109,8 @@ func (tx *CreateSubnetTx) SemanticVerify(db database.Database) (func(), error) { } for _, subnet := range subnets { - if subnet.ID.Equals(tx.ID) { - return nil, fmt.Errorf("there is already a subnet with ID %s", tx.ID) + if subnet.id.Equals(tx.id) { + return nil, fmt.Errorf("there is already a subnet with ID %s", tx.id) } } subnets = append(subnets, tx) // add new subnet @@ -152,7 +155,7 @@ func (tx *CreateSubnetTx) initialize(vm *VM) error { return err } tx.bytes = txBytes - tx.ID = ids.NewID(hashing.ComputeHash256Array(txBytes)) + tx.id = ids.NewID(hashing.ComputeHash256Array(txBytes)) return nil } @@ -160,15 +163,12 @@ func (vm *VM) newCreateSubnetTx(networkID uint32, nonce uint64, controlKeys []id threshold uint16, payerKey *crypto.PrivateKeySECP256K1R, ) (*CreateSubnetTx, error) { - tx := &CreateSubnetTx{ - UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{ - vm: vm, - NetworkID: networkID, - Nonce: nonce, - ControlKeys: controlKeys, - Threshold: threshold, - }, - } + tx := &CreateSubnetTx{UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{ + NetworkID: networkID, + Nonce: nonce, + ControlKeys: controlKeys, + Threshold: threshold, + }} unsignedIntf := interface{}(&tx.UnsignedCreateSubnetTx) unsignedBytes, err := Codec.Marshal(&unsignedIntf) diff --git a/vms/platformvm/import_tx.go b/vms/platformvm/import_tx.go index b92649f..24419b2 100644 --- a/vms/platformvm/import_tx.go +++ b/vms/platformvm/import_tx.go @@ -38,7 +38,7 @@ type UnsignedImportTx struct { Nonce uint64 `serialize:"true"` // Account that this transaction is being sent by. This is needed to ensure the Credentials are replay safe. - Account [crypto.SECP256K1RPKLen]byte `serialize:"true"` + Account ids.ShortID `serialize:"true"` Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction } @@ -129,17 +129,12 @@ func (tx *ImportTx) SyntacticVerify() error { return err } - expectedPublicKey, err := tx.vm.factory.ToPublicKey(tx.Account[:]) - if err != nil { - return err - } - key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:]) if err != nil { return err } - if !expectedPublicKey.Address().Equals(key.Address()) { + if !tx.Account.Equals(key.Address()) { return errPublicKeySignatureMismatch } @@ -236,12 +231,10 @@ func (vm *VM) newImportTx(nonce uint64, networkID uint32, ins []*ava.Transferabl tx := &ImportTx{UnsignedImportTx: UnsignedImportTx{ NetworkID: networkID, Nonce: nonce, + Account: key.PublicKey().Address(), Ins: ins, }} - pubkeyBytes := key.PublicKey().Bytes() - copy(tx.Account[:], pubkeyBytes) - unsignedIntf := interface{}(&tx.UnsignedImportTx) unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction if err != nil { diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 49f492f..a2c8240 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -16,7 +16,11 @@ import ( "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/hashing" "github.com/ava-labs/gecko/utils/json" + "github.com/ava-labs/gecko/utils/math" + "github.com/ava-labs/gecko/vms/components/ava" + "github.com/ava-labs/gecko/vms/secp256k1fx" ) var ( @@ -97,7 +101,7 @@ func (service *Service) GetSubnets(_ *http.Request, args *GetSubnetsArgs, respon response.Subnets = make([]APISubnet, len(subnets)) for i, subnet := range subnets { response.Subnets[i] = APISubnet{ - ID: subnet.ID, + ID: subnet.id, ControlKeys: subnet.ControlKeys, Threshold: json.Uint16(subnet.Threshold), } @@ -108,10 +112,10 @@ func (service *Service) GetSubnets(_ *http.Request, args *GetSubnetsArgs, respon idsSet := ids.Set{} idsSet.Add(args.IDs...) for _, subnet := range subnets { - if idsSet.Contains(subnet.ID) { + if idsSet.Contains(subnet.id) { response.Subnets = append(response.Subnets, APISubnet{ - ID: subnet.ID, + ID: subnet.id, ControlKeys: subnet.ControlKeys, Threshold: json.Uint16(subnet.Threshold), }, @@ -432,6 +436,11 @@ type genericTx struct { ****************************************************** */ +// CreateTxResponse is the response from calls to create a transaction +type CreateTxResponse struct { + UnsignedTx formatting.CB58 `json:"unsignedTx"` +} + // AddDefaultSubnetValidatorArgs are the arguments to AddDefaultSubnetValidator type AddDefaultSubnetValidatorArgs struct { APIDefaultSubnetValidator @@ -440,15 +449,9 @@ type AddDefaultSubnetValidatorArgs struct { 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 { +func (service *Service) AddDefaultSubnetValidator(_ *http.Request, args *AddDefaultSubnetValidatorArgs, reply *CreateTxResponse) error { service.vm.Ctx.Log.Debug("platform.AddDefaultSubnetValidator called") if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID @@ -490,16 +493,10 @@ type AddDefaultSubnetDelegatorArgs struct { 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 { +func (service *Service) AddDefaultSubnetDelegator(_ *http.Request, args *AddDefaultSubnetDelegatorArgs, reply *CreateTxResponse) error { service.vm.Ctx.Log.Debug("platform.AddDefaultSubnetDelegator called") if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID @@ -541,15 +538,9 @@ type AddNonDefaultSubnetValidatorArgs struct { 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 { +func (service *Service) AddNonDefaultSubnetValidator(_ *http.Request, args *AddNonDefaultSubnetValidatorArgs, response *CreateTxResponse) error { tx := addNonDefaultSubnetValidatorTx{ UnsignedAddNonDefaultSubnetValidatorTx: UnsignedAddNonDefaultSubnetValidatorTx{ SubnetValidator: SubnetValidator{ @@ -583,6 +574,83 @@ func (service *Service) AddNonDefaultSubnetValidator(_ *http.Request, args *AddN return nil } +// 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"` +} + +// 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 *CreateTxResponse) 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 +} + +// CreateExportTxArgs are the arguments to CreateExportTx +type CreateExportTxArgs struct { + // ID of the address that will receive the exported funds + To ids.ShortID `json:"to"` + + // Nonce of the account that pays the transaction fee + PayerNonce json.Uint64 `json:"payerNonce"` + + Amount json.Uint64 `json:"amount"` +} + +// CreateExportTx returns an unsigned transaction to export funds. +// The unsigned transaction must be signed with the key of [args.Payer] +func (service *Service) CreateExportTx(_ *http.Request, args *CreateExportTxArgs, response *CreateTxResponse) error { + service.vm.Ctx.Log.Debug("platform.createExportTx called") + + // Create the transaction + tx := ExportTx{UnsignedExportTx: UnsignedExportTx{ + NetworkID: service.vm.Ctx.NetworkID, + Nonce: uint64(args.PayerNonce), + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: service.vm.ava}, + Out: &secp256k1fx.TransferOutput{ + Amt: uint64(args.Amount), + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{args.To}, + }, + }, + }}, + }} + + txBytes, err := Codec.Marshal(genericTx{Tx: &tx}) + if err != nil { + return errCreatingTransaction + } + + response.UnsignedTx.Bytes = txBytes + return nil +} + /* ****************************************************** **************** Sign/Issue Txs ********************** @@ -606,7 +674,7 @@ type SignArgs struct { // SignResponse is the response from Sign type SignResponse struct { // The signed bytes - Tx formatting.CB58 + Tx formatting.CB58 `json:"tx"` } // Sign [args.bytes] @@ -642,6 +710,8 @@ 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 *ExportTx: + genTx.Tx, err = service.signExportTx(tx, key) default: err = errors.New("Could not parse given tx. Must be one of: addDefaultSubnetValidatorTx, addNonDefaultSubnetValidatorTx, createSubnetTx") } @@ -722,6 +792,29 @@ func (service *Service) signCreateSubnetTx(tx *CreateSubnetTx, key *crypto.Priva return tx, nil } +// Sign [xt] with [key] +func (service *Service) signExportTx(tx *ExportTx, key *crypto.PrivateKeySECP256K1R) (*ExportTx, error) { + service.vm.Ctx.Log.Debug("platform.signAddDefaultSubnetValidatorTx called") + + // TODO: Should we check if tx is already signed? + unsignedIntf := interface{}(&tx.UnsignedExportTx) + 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 @@ -775,6 +868,141 @@ func (service *Service) signAddNonDefaultSubnetValidatorTx(tx *addNonDefaultSubn return tx, nil } +// CreateImportTxArgs are the arguments to CreateImportTx +type CreateImportTxArgs struct { + // Addresses that can be used to sign the import + ImportAddresses []ids.ShortID `json:"importAddresses"` + + // ID of the account that will receive the imported funds, and pay the + // import fee + AccountID ids.ShortID `json:"accountID"` + + // Nonce of the account that pays the transaction fee + PayerNonce json.Uint64 `json:"payerNonce"` + + // User that controls the Addresses + Username string `json:"username"` + Password string `json:"password"` +} + +// CreateImportTx returns an unsigned transaction to import funds. +// The unsigned transaction must be signed with the key of [args.Payer] +func (service *Service) CreateImportTx(_ *http.Request, args *CreateImportTxArgs, response *SignResponse) error { + service.vm.Ctx.Log.Debug("platform.createImportTx 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} + + kc := secp256k1fx.NewKeychain() + for _, addr := range args.ImportAddresses { + key, err := user.getKey(addr) + if err != nil { + return errDB + } + kc.Add(key) + } + + key, err := user.getKey(args.AccountID) + if err != nil { + return errDB + } + kc.Add(key) + + addrs := ids.Set{} + for _, addr := range args.ImportAddresses { + addrs.Add(ids.NewID(hashing.ComputeHash256Array(addr.Bytes()))) + } + + utxos, err := service.vm.GetAtomicUTXOs(addrs) + if err != nil { + return fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err) + } + + amount := uint64(0) + time := service.vm.clock.Unix() + + ins := []*ava.TransferableInput{} + keys := [][]*crypto.PrivateKeySECP256K1R{} + for _, utxo := range utxos { + if !utxo.AssetID().Equals(service.vm.ava) { + continue + } + inputIntf, signers, err := kc.Spend(utxo.Out, time) + if err != nil { + continue + } + input, ok := inputIntf.(ava.Transferable) + if !ok { + continue + } + spent, err := math.Add64(amount, input.Amount()) + if err != nil { + return err + } + amount = spent + + in := &ava.TransferableInput{ + UTXOID: utxo.UTXOID, + Asset: ava.Asset{ID: service.vm.ava}, + In: input, + } + + ins = append(ins, in) + keys = append(keys, signers) + } + + ava.SortTransferableInputsWithSigners(ins, keys) + + // Create the transaction + tx := ImportTx{UnsignedImportTx: UnsignedImportTx{ + NetworkID: service.vm.Ctx.NetworkID, + Nonce: uint64(args.PayerNonce), + Account: args.AccountID, + Ins: ins, + }} + + // TODO: Should we check if tx is already signed? + unsignedIntf := interface{}(&tx.UnsignedImportTx) + unsignedTxBytes, err := Codec.Marshal(&unsignedIntf) + if err != nil { + return fmt.Errorf("error serializing unsigned tx: %w", err) + } + hash := hashing.ComputeHash256(unsignedTxBytes) + + sig, err := key.SignHash(unsignedTxBytes) + if err != nil { + return errors.New("error while signing") + } + copy(tx.Sig[:], sig) + + for _, credKeys := range keys { + cred := &secp256k1fx.Credential{} + for _, key := range credKeys { + sig, err := key.SignHash(hash) + if err != nil { + return fmt.Errorf("problem creating transaction: %w", err) + } + fixedSig := [crypto.SECP256K1RSigLen]byte{} + copy(fixedSig[:], sig) + + cred.Sigs = append(cred.Sigs, fixedSig) + } + tx.Creds = append(tx.Creds, cred) + } + + txBytes, err := Codec.Marshal(genericTx{Tx: &tx}) + if err != nil { + return errCreatingTransaction + } + + response.Tx.Bytes = txBytes + return nil +} + // IssueTxArgs are the arguments to IssueTx type IssueTxArgs struct { // Tx being sent to the network @@ -800,69 +1028,25 @@ func (service *Service) IssueTx(_ *http.Request, args *IssueTxArgs, response *Is 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: + case DecisionTx: 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 + response.TxID = tx.ID() + case AtomicTx: + if err := tx.initialize(service.vm); err != nil { + return fmt.Errorf("error initializing tx: %s", err) + } + service.vm.unissuedAtomicTxs = append(service.vm.unissuedAtomicTxs, tx) + response.TxID = tx.ID() 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, + return errors.New("Could not parse given tx. Must be a TimedTx, DecisionTx, or AtomicTx") } - txBytes, err := Codec.Marshal(genericTx{Tx: &tx}) - if err != nil { - return errCreatingTransaction - } - - response.UnsignedTx.Bytes = txBytes + service.vm.resetTimer() return nil - } /* @@ -1028,3 +1212,176 @@ func (service *Service) chainExists(blockID ids.ID, chainID ids.ID) (bool, error return false, nil } + +// // SendExportArgs are arguments for passing into SendExport requests +// type SendExportArgs struct { +// Username string `json:"username"` +// Password string `json:"password"` +// Amount json.Uint64 `json:"amount"` +// To string `json:"to"` +// } + +// // SendExportReply defines the Send replies returned from the API +// type SendExportReply struct { +// TxID ids.ID `json:"txID"` +// } + +// // SendExport returns the ID of the newly created atomic transaction +// func (service *Service) SendExport(_ *http.Request, args *SendExportArgs, reply *SendExportReply) error { +// service.vm.ctx.Log.Verbo("SendExport called with username: %s", args.Username) + +// if args.Amount == 0 { +// return errInvalidAmount +// } + +// toBytes, err := service.vm.Parse(args.To) +// if err != nil { +// return fmt.Errorf("problem parsing to address: %w", err) +// } +// to, err := ids.ToShortID(toBytes) +// if err != nil { +// return fmt.Errorf("problem parsing to address: %w", err) +// } + +// db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) +// if err != nil { +// return fmt.Errorf("problem retrieving user: %w", err) +// } + +// user := userState{vm: service.vm} + +// addresses, _ := user.Addresses(db) + +// addrs := ids.Set{} +// addrs.Add(addresses...) +// utxos, err := service.vm.GetUTXOs(addrs) +// if err != nil { +// return fmt.Errorf("problem retrieving user's UTXOs: %w", err) +// } + +// kc := secp256k1fx.NewKeychain() +// for _, addr := range addresses { +// sk, err := user.Key(db, addr) +// if err != nil { +// return fmt.Errorf("problem retrieving private key: %w", err) +// } +// kc.Add(sk) +// } + +// amountSpent := uint64(0) +// time := service.vm.clock.Unix() + +// ins := []*ava.TransferableInput{} +// keys := [][]*crypto.PrivateKeySECP256K1R{} +// for _, utxo := range utxos { +// if !utxo.AssetID().Equals(service.vm.ava) { +// continue +// } +// inputIntf, signers, err := kc.Spend(utxo.Out, time) +// if err != nil { +// continue +// } +// input, ok := inputIntf.(ava.Transferable) +// if !ok { +// continue +// } +// spent, err := math.Add64(amountSpent, input.Amount()) +// if err != nil { +// return errSpendOverflow +// } +// amountSpent = spent + +// in := &ava.TransferableInput{ +// UTXOID: utxo.UTXOID, +// Asset: ava.Asset{ID: service.vm.ava}, +// In: input, +// } + +// ins = append(ins, in) +// keys = append(keys, signers) + +// if amountSpent >= uint64(args.Amount) { +// break +// } +// } + +// if amountSpent < uint64(args.Amount) { +// return errInsufficientFunds +// } + +// ava.SortTransferableInputsWithSigners(ins, keys) + +// exportOuts := []*ava.TransferableOutput{&ava.TransferableOutput{ +// Asset: ava.Asset{ID: service.vm.ava}, +// Out: &secp256k1fx.TransferOutput{ +// Amt: uint64(args.Amount), +// Locktime: 0, +// OutputOwners: secp256k1fx.OutputOwners{ +// Threshold: 1, +// Addrs: []ids.ShortID{to}, +// }, +// }, +// }} + +// outs := []*ava.TransferableOutput{} +// if amountSpent > uint64(args.Amount) { +// changeAddr := kc.Keys[0].PublicKey().Address() +// outs = append(outs, &ava.TransferableOutput{ +// Asset: ava.Asset{ID: service.vm.ava}, +// Out: &secp256k1fx.TransferOutput{ +// Amt: amountSpent - uint64(args.Amount), +// Locktime: 0, +// OutputOwners: secp256k1fx.OutputOwners{ +// Threshold: 1, +// Addrs: []ids.ShortID{changeAddr}, +// }, +// }, +// }) +// } + +// ava.SortTransferableOutputs(outs, service.vm.codec) + +// tx := Tx{UnsignedTx: &ExportTx{ +// BaseTx: BaseTx{ +// NetID: service.vm.ctx.NetworkID, +// BCID: service.vm.ctx.ChainID, +// Outs: outs, +// Ins: ins, +// }, +// Outs: exportOuts, +// }} + +// unsignedBytes, err := service.vm.codec.Marshal(&tx.UnsignedTx) +// if err != nil { +// return fmt.Errorf("problem creating transaction: %w", err) +// } +// hash := hashing.ComputeHash256(unsignedBytes) + +// for _, credKeys := range keys { +// cred := &secp256k1fx.Credential{} +// for _, key := range credKeys { +// sig, err := key.SignHash(hash) +// if err != nil { +// return fmt.Errorf("problem creating transaction: %w", err) +// } +// fixedSig := [crypto.SECP256K1RSigLen]byte{} +// copy(fixedSig[:], sig) + +// cred.Sigs = append(cred.Sigs, fixedSig) +// } +// tx.Creds = append(tx.Creds, cred) +// } + +// b, err := service.vm.codec.Marshal(tx) +// if err != nil { +// return fmt.Errorf("problem creating transaction: %w", err) +// } + +// txID, err := service.vm.IssueTx(b, nil) +// if err != nil { +// return fmt.Errorf("problem issuing transaction: %w", err) +// } + +// reply.TxID = txID +// return nil +// } diff --git a/vms/platformvm/standard_block.go b/vms/platformvm/standard_block.go index f3c89c9..5f7e300 100644 --- a/vms/platformvm/standard_block.go +++ b/vms/platformvm/standard_block.go @@ -12,6 +12,8 @@ import ( // DecisionTx is an operation that can be decided without being proposed type DecisionTx interface { + ID() ids.ID + initialize(vm *VM) error // Attempt to verify this transaction with the provided state. The provided diff --git a/vms/platformvm/state.go b/vms/platformvm/state.go index 9febf25..7bc45da 100644 --- a/vms/platformvm/state.go +++ b/vms/platformvm/state.go @@ -211,18 +211,18 @@ func (vm *VM) getSubnets(db database.Database) ([]*CreateSubnetTx, error) { } // get the subnet with the specified ID -func (vm *VM) getSubnet(db database.Database, ID ids.ID) (*CreateSubnetTx, error) { +func (vm *VM) getSubnet(db database.Database, id ids.ID) (*CreateSubnetTx, error) { subnets, err := vm.getSubnets(db) if err != nil { return nil, err } for _, subnet := range subnets { - if subnet.ID.Equals(ID) { + if subnet.id.Equals(id) { return subnet, nil } } - return nil, fmt.Errorf("couldn't find subnet with ID %s", ID) + return nil, fmt.Errorf("couldn't find subnet with ID %s", id) } // register each type that we'll be storing in the database diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index ed2dfdc..0db5136 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -24,6 +24,7 @@ import ( "github.com/ava-labs/gecko/utils/timer" "github.com/ava-labs/gecko/utils/units" "github.com/ava-labs/gecko/utils/wrappers" + "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/codec" "github.com/ava-labs/gecko/vms/components/core" "github.com/ava-labs/gecko/vms/secp256k1fx" @@ -632,7 +633,7 @@ func (vm *VM) nextValidatorChangeTime(db database.Database, start bool) time.Tim return earliest } for _, subnet := range subnets { - t := vm.nextSubnetValidatorChangeTime(db, subnet.ID, start) + t := vm.nextSubnetValidatorChangeTime(db, subnet.id, start) if t.Before(earliest) { earliest = t } @@ -740,3 +741,28 @@ func (vm *VM) Codec() codec.Codec { return vm.codec } // Clock ... func (vm *VM) Clock() *timer.Clock { return &vm.clock } + +// GetAtomicUTXOs returns the utxos that at least one of the provided addresses is +// referenced in. +func (vm *VM) GetAtomicUTXOs(addrs ids.Set) ([]*ava.UTXO, error) { + smDB := vm.Ctx.SharedMemory.GetDatabase(vm.avm) + defer vm.Ctx.SharedMemory.ReleaseDatabase(vm.avm) + + state := ava.NewPrefixedState(smDB, vm.codec) + + utxoIDs := ids.Set{} + for _, addr := range addrs.List() { + utxos, _ := state.AVMFunds(addr) + utxoIDs.Add(utxos...) + } + + utxos := []*ava.UTXO{} + for _, utxoID := range utxoIDs.List() { + utxo, err := state.AVMUTXO(utxoID) + if err != nil { + return nil, err + } + utxos = append(utxos, utxo) + } + return utxos, nil +} diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index d19b06c..00e436d 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -153,7 +153,7 @@ func defaultVM() *VM { &EventHeap{ SortByStartTime: false, }, - tx.ID, + tx.id, ) if err != nil { panic(err) @@ -163,7 +163,7 @@ func defaultVM() *VM { &EventHeap{ SortByStartTime: true, }, - tx.ID, + tx.id, ) if err != nil { panic(err) @@ -437,7 +437,7 @@ func TestAddNonDefaultSubnetValidatorAccept(t *testing.T) { uint64(startTime.Unix()), uint64(endTime.Unix()), keys[0].PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, keys[0], @@ -482,7 +482,7 @@ func TestAddNonDefaultSubnetValidatorAccept(t *testing.T) { commit.Accept() // accept the proposal // Verify that new validator is in pending validator set - pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.ID) + pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.id) if err != nil { t.Fatal(err) } @@ -510,7 +510,7 @@ func TestAddNonDefaultSubnetValidatorReject(t *testing.T) { uint64(startTime.Unix()), uint64(endTime.Unix()), keys[0].PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[1], testSubnet1ControlKeys[2]}, keys[0], @@ -555,7 +555,7 @@ func TestAddNonDefaultSubnetValidatorReject(t *testing.T) { abort.Accept() // reject the proposal // Verify that new validator NOT in pending validator set - pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.ID) + pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.id) if err != nil { t.Fatal(err) } @@ -885,7 +885,7 @@ func TestCreateSubnet(t *testing.T) { uint64(startTime.Unix()), uint64(endTime.Unix()), keys[0].PublicKey().Address(), - createSubnetTx.ID, + createSubnetTx.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{keys[0]}, keys[0], @@ -935,7 +935,7 @@ func TestCreateSubnet(t *testing.T) { commit.Accept() // add the validator to pending validator set // Verify validator is in pending validator set - pendingValidators, err := vm.getPendingValidators(vm.DB, createSubnetTx.ID) + pendingValidators, err := vm.getPendingValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } @@ -989,7 +989,7 @@ func TestCreateSubnet(t *testing.T) { // Verify validator no longer in pending validator set // Verify validator is in pending validator set - pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.ID) + pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } @@ -998,7 +998,7 @@ func TestCreateSubnet(t *testing.T) { } // Verify validator is in current validator set - currentValidators, err := vm.getCurrentValidators(vm.DB, createSubnetTx.ID) + currentValidators, err := vm.getCurrentValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } @@ -1048,14 +1048,14 @@ func TestCreateSubnet(t *testing.T) { commit.Accept() // remove validator from current validator set // pending validators and current validator should be empty - pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.ID) + pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } if pendingValidators.Len() != 0 { t.Fatal("pending validator set should be empty") } - currentValidators, err = vm.getCurrentValidators(vm.DB, createSubnetTx.ID) + currentValidators, err = vm.getCurrentValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) }