mirror of https://github.com/poanetwork/gecko.git
finished first pass of atomic swaps
This commit is contained in:
parent
93ed25f878
commit
3efccbf354
|
@ -8,11 +8,9 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow/choices"
|
||||
"github.com/ava-labs/gecko/utils"
|
||||
"github.com/ava-labs/gecko/utils/crypto"
|
||||
"github.com/ava-labs/gecko/utils/formatting"
|
||||
"github.com/ava-labs/gecko/utils/hashing"
|
||||
|
@ -639,7 +637,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
|
|||
return errInsufficientFunds
|
||||
}
|
||||
|
||||
SortTransferableInputsWithSigners(ins, keys)
|
||||
ava.SortTransferableInputsWithSigners(ins, keys)
|
||||
|
||||
outs := []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
|
@ -714,42 +712,6 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
|
|||
return nil
|
||||
}
|
||||
|
||||
type innerSortTransferableInputsWithSigners struct {
|
||||
ins []*ava.TransferableInput
|
||||
signers [][]*crypto.PrivateKeySECP256K1R
|
||||
}
|
||||
|
||||
func (ins *innerSortTransferableInputsWithSigners) Less(i, j int) bool {
|
||||
iID, iIndex := ins.ins[i].InputSource()
|
||||
jID, jIndex := ins.ins[j].InputSource()
|
||||
|
||||
switch bytes.Compare(iID.Bytes(), jID.Bytes()) {
|
||||
case -1:
|
||||
return true
|
||||
case 0:
|
||||
return iIndex < jIndex
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
func (ins *innerSortTransferableInputsWithSigners) Len() int { return len(ins.ins) }
|
||||
func (ins *innerSortTransferableInputsWithSigners) Swap(i, j int) {
|
||||
ins.ins[j], ins.ins[i] = ins.ins[i], ins.ins[j]
|
||||
ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j]
|
||||
}
|
||||
|
||||
// SortTransferableInputsWithSigners sorts the inputs and signers based on the
|
||||
// input's utxo ID
|
||||
func SortTransferableInputsWithSigners(ins []*ava.TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) {
|
||||
sort.Sort(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
|
||||
}
|
||||
|
||||
// IsSortedAndUniqueTransferableInputsWithSigners returns true if the inputs are
|
||||
// sorted and unique
|
||||
func IsSortedAndUniqueTransferableInputsWithSigners(ins []*ava.TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) bool {
|
||||
return utils.IsSortedAndUnique(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
|
||||
}
|
||||
|
||||
// CreateMintTxArgs are arguments for passing into CreateMintTx requests
|
||||
type CreateMintTxArgs struct {
|
||||
Amount json.Uint64 `json:"amount"`
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"sort"
|
||||
|
||||
"github.com/ava-labs/gecko/utils"
|
||||
"github.com/ava-labs/gecko/utils/crypto"
|
||||
"github.com/ava-labs/gecko/vms/components/codec"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
)
|
||||
|
@ -143,3 +144,39 @@ func SortTransferableInputs(ins []*TransferableInput) { sort.Sort(innerSortTrans
|
|||
func IsSortedAndUniqueTransferableInputs(ins []*TransferableInput) bool {
|
||||
return utils.IsSortedAndUnique(innerSortTransferableInputs(ins))
|
||||
}
|
||||
|
||||
type innerSortTransferableInputsWithSigners struct {
|
||||
ins []*TransferableInput
|
||||
signers [][]*crypto.PrivateKeySECP256K1R
|
||||
}
|
||||
|
||||
func (ins *innerSortTransferableInputsWithSigners) Less(i, j int) bool {
|
||||
iID, iIndex := ins.ins[i].InputSource()
|
||||
jID, jIndex := ins.ins[j].InputSource()
|
||||
|
||||
switch bytes.Compare(iID.Bytes(), jID.Bytes()) {
|
||||
case -1:
|
||||
return true
|
||||
case 0:
|
||||
return iIndex < jIndex
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
func (ins *innerSortTransferableInputsWithSigners) Len() int { return len(ins.ins) }
|
||||
func (ins *innerSortTransferableInputsWithSigners) Swap(i, j int) {
|
||||
ins.ins[j], ins.ins[i] = ins.ins[i], ins.ins[j]
|
||||
ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j]
|
||||
}
|
||||
|
||||
// SortTransferableInputsWithSigners sorts the inputs and signers based on the
|
||||
// input's utxo ID
|
||||
func SortTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) {
|
||||
sort.Sort(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
|
||||
}
|
||||
|
||||
// IsSortedAndUniqueTransferableInputsWithSigners returns true if the inputs are
|
||||
// sorted and unique
|
||||
func IsSortedAndUniqueTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) bool {
|
||||
return utils.IsSortedAndUnique(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
|
||||
}
|
||||
|
|
|
@ -67,6 +67,13 @@ func (ab *AtomicBlock) conflicts(s ids.Set) bool {
|
|||
// This function also sets onAcceptDB database if the verification passes.
|
||||
func (ab *AtomicBlock) Verify() error {
|
||||
parentBlock := ab.parentBlock()
|
||||
|
||||
ab.inputs = ab.Tx.InputUTXOs()
|
||||
|
||||
if parentBlock.conflicts(ab.inputs) {
|
||||
return errConflictingParentTxs
|
||||
}
|
||||
|
||||
// AtomicBlock is not a modifier on a proposal block, so its parent must be
|
||||
// a decision.
|
||||
parent, ok := parentBlock.(decision)
|
||||
|
@ -80,11 +87,6 @@ func (ab *AtomicBlock) Verify() error {
|
|||
if err := ab.Tx.SemanticVerify(ab.onAcceptDB); err != nil {
|
||||
return err
|
||||
}
|
||||
ab.inputs = ab.Tx.InputUTXOs()
|
||||
|
||||
if parentBlock.conflicts(ab.inputs) {
|
||||
return errConflictingParentTxs
|
||||
}
|
||||
|
||||
ab.vm.currentBlocks[ab.ID().Key()] = ab
|
||||
ab.parentBlock().addChild(ab)
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestTxHeapStart(t *testing.T) {
|
|||
123, // stake amount
|
||||
1, // startTime
|
||||
3, // endTime
|
||||
ids.NewShortID([20]byte{1}), // node ID
|
||||
ids.NewShortID([20]byte{}), // node ID
|
||||
ids.NewShortID([20]byte{1, 2, 3, 4, 5, 6, 7}), // destination
|
||||
0, // shares
|
||||
0, // network ID
|
||||
|
@ -33,7 +33,7 @@ func TestTxHeapStart(t *testing.T) {
|
|||
123, // stake amount
|
||||
1, // startTime
|
||||
3, // endTime
|
||||
ids.NewShortID([20]byte{}), // node ID
|
||||
ids.NewShortID([20]byte{1}), // node ID
|
||||
ids.NewShortID([20]byte{1, 2, 3, 4, 5, 6, 7}), // destination
|
||||
0, // shares
|
||||
0, // network ID
|
||||
|
@ -85,7 +85,7 @@ func TestTxHeapStop(t *testing.T) {
|
|||
123, // stake amount
|
||||
1, // startTime
|
||||
3, // endTime
|
||||
ids.NewShortID([20]byte{1}), // node ID
|
||||
ids.NewShortID([20]byte{}), // node ID
|
||||
ids.NewShortID([20]byte{1, 2, 3, 4, 5, 6, 7}), // destination
|
||||
0, // shares
|
||||
0, // network ID
|
||||
|
@ -100,7 +100,7 @@ func TestTxHeapStop(t *testing.T) {
|
|||
123, // stake amount
|
||||
1, // startTime
|
||||
3, // endTime
|
||||
ids.NewShortID([20]byte{}), // node ID
|
||||
ids.NewShortID([20]byte{1}), // node ID
|
||||
ids.NewShortID([20]byte{1, 2, 3, 4, 5, 6, 7}), // destination
|
||||
0, // shares
|
||||
0, // network ID
|
||||
|
|
|
@ -3,166 +3,199 @@
|
|||
|
||||
package platformvm
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
// "github.com/ava-labs/gecko/chains"
|
||||
// "github.com/ava-labs/gecko/database"
|
||||
// "github.com/ava-labs/gecko/ids"
|
||||
// "github.com/ava-labs/gecko/utils/crypto"
|
||||
// "github.com/ava-labs/gecko/utils/hashing"
|
||||
// "github.com/ava-labs/gecko/vms/components/ava"
|
||||
// )
|
||||
"github.com/ava-labs/gecko/chains/atomic"
|
||||
"github.com/ava-labs/gecko/database"
|
||||
"github.com/ava-labs/gecko/database/versiondb"
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow/choices"
|
||||
"github.com/ava-labs/gecko/utils/crypto"
|
||||
"github.com/ava-labs/gecko/utils/hashing"
|
||||
"github.com/ava-labs/gecko/utils/math"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
)
|
||||
|
||||
// // UnsignedExportTx is an unsigned ExportTx
|
||||
// type UnsignedExportTx struct {
|
||||
// // ID of the network this blockchain exists on
|
||||
// NetworkID uint32 `serialize:"true"`
|
||||
var (
|
||||
errNoExportOutputs = errors.New("no export outputs")
|
||||
errOutputsNotSorted = errors.New("outputs not sorted")
|
||||
)
|
||||
|
||||
// // Next unused nonce of account paying the transaction fee for this transaction.
|
||||
// // Currently unused, as there are no tx fees.
|
||||
// Nonce uint64 `serialize:"true"`
|
||||
// UnsignedExportTx is an unsigned ExportTx
|
||||
type UnsignedExportTx struct {
|
||||
// ID of the network this blockchain exists on
|
||||
NetworkID uint32 `serialize:"true"`
|
||||
|
||||
// Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction
|
||||
// }
|
||||
// Next unused nonce of account paying for this transaction.
|
||||
Nonce uint64 `serialize:"true"`
|
||||
|
||||
// // ExportTx exports funds to the AVM
|
||||
// type ExportTx struct {
|
||||
// UnsignedExportTx `serialize:"true"`
|
||||
Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction
|
||||
}
|
||||
|
||||
// Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"`
|
||||
// ExportTx exports funds to the AVM
|
||||
type ExportTx struct {
|
||||
UnsignedExportTx `serialize:"true"`
|
||||
|
||||
// vm *VM
|
||||
// id ids.ID
|
||||
// key crypto.PublicKey // public key of transaction signer
|
||||
// bytes []byte
|
||||
// }
|
||||
Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"`
|
||||
|
||||
// func (tx *ExportTx) initialize(vm *VM) error {
|
||||
// tx.vm = vm
|
||||
// txBytes, err := Codec.Marshal(tx) // byte repr. of the signed tx
|
||||
// tx.bytes = txBytes
|
||||
// tx.id = ids.NewID(hashing.ComputeHash256Array(txBytes))
|
||||
// return err
|
||||
// }
|
||||
vm *VM
|
||||
id ids.ID
|
||||
key crypto.PublicKey // public key of transaction signer
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
// // ID of this transaction
|
||||
// func (tx *ExportTx) ID() ids.ID { return tx.id }
|
||||
func (tx *ExportTx) initialize(vm *VM) error {
|
||||
tx.vm = vm
|
||||
txBytes, err := Codec.Marshal(tx) // byte repr. of the signed tx
|
||||
tx.bytes = txBytes
|
||||
tx.id = ids.NewID(hashing.ComputeHash256Array(txBytes))
|
||||
return err
|
||||
}
|
||||
|
||||
// // Key returns the public key of the signer of this transaction
|
||||
// // Precondition: tx.Verify() has been called and returned nil
|
||||
// func (tx *ExportTx) Key() crypto.PublicKey { return tx.key }
|
||||
// ID of this transaction
|
||||
func (tx *ExportTx) ID() ids.ID { return tx.id }
|
||||
|
||||
// // Bytes returns the byte representation of a CreateChainTx
|
||||
// func (tx *ExportTx) Bytes() []byte { return tx.bytes }
|
||||
// Key returns the public key of the signer of this transaction
|
||||
// Precondition: tx.Verify() has been called and returned nil
|
||||
func (tx *ExportTx) Key() crypto.PublicKey { return tx.key }
|
||||
|
||||
// // InputUTXOs returns an empty set
|
||||
// func (tx *ExportTx) InputUTXOs() ids.Set { return ids.Set{} }
|
||||
// Bytes returns the byte representation of an ExportTx
|
||||
func (tx *ExportTx) Bytes() []byte { return tx.bytes }
|
||||
|
||||
// // SyntacticVerify this transaction is well-formed
|
||||
// // Also populates [tx.Key] with the public key that signed this transaction
|
||||
// func (tx *ExportTx) SyntacticVerify() error {
|
||||
// switch {
|
||||
// case tx == nil:
|
||||
// return errNilTx
|
||||
// case tx.key != nil:
|
||||
// return nil // Only verify the transaction once
|
||||
// case tx.NetworkID != tx.vm.Ctx.NetworkID: // verify the transaction is on this network
|
||||
// return errWrongNetworkID
|
||||
// case tx.id.IsZero():
|
||||
// return errInvalidID
|
||||
// }
|
||||
// InputUTXOs returns an empty set
|
||||
func (tx *ExportTx) InputUTXOs() ids.Set { return ids.Set{} }
|
||||
|
||||
// unsignedIntf := interface{}(&tx.UnsignedImportTx)
|
||||
// unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr of unsigned tx
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// SyntacticVerify this transaction is well-formed
|
||||
// Also populates [tx.Key] with the public key that signed this transaction
|
||||
func (tx *ExportTx) SyntacticVerify() error {
|
||||
switch {
|
||||
case tx == nil:
|
||||
return errNilTx
|
||||
case tx.key != nil:
|
||||
return nil // Only verify the transaction once
|
||||
case tx.NetworkID != tx.vm.Ctx.NetworkID: // verify the transaction is on this network
|
||||
return errWrongNetworkID
|
||||
case tx.id.IsZero():
|
||||
return errInvalidID
|
||||
case len(tx.Outs) == 0:
|
||||
return errNoExportOutputs
|
||||
}
|
||||
|
||||
// key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:])
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// tx.key = key
|
||||
for _, out := range tx.Outs {
|
||||
if err := out.Verify(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !out.AssetID().Equals(tx.vm.AVA) {
|
||||
return errUnknownAsset
|
||||
}
|
||||
}
|
||||
if !ava.IsSortedTransferableOutputs(tx.Outs, Codec) {
|
||||
return errOutputsNotSorted
|
||||
}
|
||||
|
||||
// return nil
|
||||
// }
|
||||
unsignedIntf := interface{}(&tx.UnsignedExportTx)
|
||||
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr of unsigned tx
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// // SemanticVerify this transaction is valid.
|
||||
// func (tx *ExportTx) SemanticVerify(db database.Database) (func(), error) {
|
||||
// if err := tx.SyntacticVerify(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// currentChains, err := tx.vm.getChains(db) // chains that currently exist
|
||||
// if err != nil {
|
||||
// return nil, errDBChains
|
||||
// }
|
||||
// for _, chain := range currentChains {
|
||||
// if chain.ID().Equals(tx.ID()) {
|
||||
// return nil, fmt.Errorf("chain with ID %s already exists", chain.ID())
|
||||
// }
|
||||
// }
|
||||
// currentChains = append(currentChains, tx) // add this new chain
|
||||
// if err := tx.vm.putChains(db, currentChains); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
tx.key = key
|
||||
return nil
|
||||
}
|
||||
|
||||
// // Deduct tx fee from payer's account
|
||||
// account, err := tx.vm.getAccount(db, tx.Key().Address())
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// account, err = account.Remove(0, tx.Nonce)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if err := tx.vm.putAccount(db, account); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// SemanticVerify this transaction is valid.
|
||||
func (tx *ExportTx) SemanticVerify(db database.Database) error {
|
||||
if err := tx.SyntacticVerify(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// // If this proposal is committed, create the new blockchain using the chain manager
|
||||
// onAccept := func() {
|
||||
// chainParams := chains.ChainParameters{
|
||||
// ID: tx.ID(),
|
||||
// GenesisData: tx.GenesisData,
|
||||
// VMAlias: tx.VMID.String(),
|
||||
// }
|
||||
// for _, fxID := range tx.FxIDs {
|
||||
// chainParams.FxAliases = append(chainParams.FxAliases, fxID.String())
|
||||
// }
|
||||
// // TODO: Not sure how else to make this not nil pointer error during tests
|
||||
// if tx.vm.ChainManager != nil {
|
||||
// tx.vm.ChainManager.CreateChain(chainParams)
|
||||
// }
|
||||
// }
|
||||
amount := uint64(0)
|
||||
for _, out := range tx.Outs {
|
||||
newAmount, err := math.Add64(out.Out.Amount(), amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount = newAmount
|
||||
}
|
||||
|
||||
// return onAccept, nil
|
||||
// }
|
||||
accountID := tx.key.Address()
|
||||
account, err := tx.vm.getAccount(db, accountID)
|
||||
if err != nil {
|
||||
return errDBAccount
|
||||
}
|
||||
|
||||
// func (vm *VM) newExportTx(nonce uint64, genesisData []byte, vmID ids.ID, fxIDs []ids.ID, chainName string, networkID uint32, key *crypto.PrivateKeySECP256K1R) (*ExportTx, error) {
|
||||
// tx := &CreateChainTx{
|
||||
// UnsignedCreateChainTx: UnsignedCreateChainTx{
|
||||
// NetworkID: networkID,
|
||||
// Nonce: nonce,
|
||||
// GenesisData: genesisData,
|
||||
// VMID: vmID,
|
||||
// FxIDs: fxIDs,
|
||||
// ChainName: chainName,
|
||||
// },
|
||||
// }
|
||||
account, err = account.Remove(amount, tx.Nonce)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.vm.putAccount(db, account)
|
||||
}
|
||||
|
||||
// unsignedIntf := interface{}(&tx.UnsignedCreateChainTx)
|
||||
// unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// Accept this transaction.
|
||||
func (tx *ExportTx) Accept(batch database.Batch) error {
|
||||
txID := tx.ID()
|
||||
|
||||
// sig, err := key.Sign(unsignedBytes)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// copy(tx.Sig[:], sig)
|
||||
bID := ids.Empty // TODO: Needs to be set to the platform chain
|
||||
smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID)
|
||||
defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID)
|
||||
|
||||
// return tx, tx.initialize(vm)
|
||||
// }
|
||||
vsmDB := versiondb.New(smDB)
|
||||
|
||||
state := ava.NewPrefixedState(vsmDB, Codec)
|
||||
for i, out := range tx.Outs {
|
||||
utxo := &ava.UTXO{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: txID,
|
||||
OutputIndex: uint32(i),
|
||||
},
|
||||
Asset: ava.Asset{ID: out.AssetID()},
|
||||
Out: out.Out,
|
||||
}
|
||||
|
||||
utxoID := utxo.InputID()
|
||||
if _, err := state.PlatformStatus(utxoID); err == nil {
|
||||
if err := state.SetPlatformStatus(utxoID, choices.Unknown); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := state.SetPlatformUTXO(utxoID, utxo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sharedBatch, err := vsmDB.CommitBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return atomic.WriteAll(batch, sharedBatch)
|
||||
}
|
||||
|
||||
func (vm *VM) newExportTx(nonce uint64, networkID uint32, outs []*ava.TransferableOutput, from *crypto.PrivateKeySECP256K1R) (*ExportTx, error) {
|
||||
ava.SortTransferableOutputs(outs, Codec)
|
||||
|
||||
tx := &ExportTx{UnsignedExportTx: UnsignedExportTx{
|
||||
NetworkID: networkID,
|
||||
Nonce: nonce,
|
||||
Outs: outs,
|
||||
}}
|
||||
|
||||
unsignedIntf := interface{}(&tx.UnsignedExportTx)
|
||||
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig, err := key.Sign(unsignedBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(tx.Sig[:], sig)
|
||||
|
||||
return tx, tx.initialize(vm)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package platformvm
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ava-labs/gecko/chains/atomic"
|
||||
"github.com/ava-labs/gecko/database"
|
||||
|
@ -16,6 +17,7 @@ import (
|
|||
"github.com/ava-labs/gecko/utils/math"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
"github.com/ava-labs/gecko/vms/secp256k1fx"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -32,8 +34,8 @@ type UnsignedImportTx struct {
|
|||
// ID of the network this blockchain exists on
|
||||
NetworkID uint32 `serialize:"true"`
|
||||
|
||||
// Next unused nonce of account paying the transaction fee for this transaction.
|
||||
// Currently unused, as there are no tx fees.
|
||||
// Next unused nonce of account paying the transaction fee and receiving the
|
||||
// inputs of this transaction.
|
||||
Nonce uint64 `serialize:"true"`
|
||||
|
||||
// Account that this transaction is being sent by. This is needed to ensure the Credentials are replay safe.
|
||||
|
@ -49,10 +51,11 @@ type ImportTx struct {
|
|||
Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"`
|
||||
Creds []verify.Verifiable `serialize:"true"` // The credentials of this transaction
|
||||
|
||||
vm *VM
|
||||
id ids.ID
|
||||
key crypto.PublicKey // public key of transaction signer
|
||||
bytes []byte
|
||||
vm *VM
|
||||
id ids.ID
|
||||
key crypto.PublicKey // public key of transaction signer
|
||||
unsignedBytes []byte
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
func (tx *ImportTx) initialize(vm *VM) error {
|
||||
|
@ -70,7 +73,10 @@ func (tx *ImportTx) ID() ids.ID { return tx.id }
|
|||
// Precondition: tx.Verify() has been called and returned nil
|
||||
func (tx *ImportTx) Key() crypto.PublicKey { return tx.key }
|
||||
|
||||
// Bytes returns the byte representation of a CreateChainTx
|
||||
// UnsignedBytes returns the unsigned byte representation of an ImportTx
|
||||
func (tx *ImportTx) UnsignedBytes() []byte { return tx.unsignedBytes }
|
||||
|
||||
// Bytes returns the byte representation of an ImportTx
|
||||
func (tx *ImportTx) Bytes() []byte { return tx.bytes }
|
||||
|
||||
// InputUTXOs returns an empty set
|
||||
|
@ -139,6 +145,7 @@ func (tx *ImportTx) SyntacticVerify() error {
|
|||
}
|
||||
|
||||
tx.key = key
|
||||
tx.unsignedBytes = unsignedBytes
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -148,36 +155,13 @@ func (tx *ImportTx) SemanticVerify(db database.Database) error {
|
|||
return err
|
||||
}
|
||||
|
||||
bID := ids.Empty // TODO: Needs to be set to the platform chain
|
||||
smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID)
|
||||
defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID)
|
||||
|
||||
state := ava.NewPrefixedState(smDB, Codec)
|
||||
|
||||
amount := uint64(0)
|
||||
for i, in := range tx.Ins {
|
||||
for _, in := range tx.Ins {
|
||||
newAmount, err := math.Add64(in.In.Amount(), amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount = newAmount
|
||||
|
||||
cred := tx.Creds[i]
|
||||
|
||||
utxoID := in.UTXOID.InputID()
|
||||
utxo, err := state.AVMUTXO(utxoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utxoAssetID := utxo.AssetID()
|
||||
inAssetID := in.AssetID()
|
||||
if !utxoAssetID.Equals(inAssetID) {
|
||||
return errAssetIDMismatch
|
||||
}
|
||||
|
||||
if err := tx.vm.fx.VerifyTransfer(uTx, utxo.Out, in.In, cred); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Deduct tx fee from payer's account
|
||||
|
@ -193,7 +177,36 @@ func (tx *ImportTx) SemanticVerify(db database.Database) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.vm.putAccount(db, account)
|
||||
if err := tx.vm.putAccount(db, account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bID := ids.Empty // TODO: Needs to be set to the platform chain
|
||||
smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID)
|
||||
defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID)
|
||||
|
||||
state := ava.NewPrefixedState(smDB, Codec)
|
||||
|
||||
for i, in := range tx.Ins {
|
||||
cred := tx.Creds[i]
|
||||
|
||||
utxoID := in.UTXOID.InputID()
|
||||
utxo, err := state.AVMUTXO(utxoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utxoAssetID := utxo.AssetID()
|
||||
inAssetID := in.AssetID()
|
||||
if !utxoAssetID.Equals(inAssetID) {
|
||||
return errAssetIDMismatch
|
||||
}
|
||||
|
||||
if err := tx.vm.fx.VerifyTransfer(tx, utxo.Out, in.In, cred); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Accept this transaction.
|
||||
|
@ -224,25 +237,42 @@ func (tx *ImportTx) Accept(batch database.Batch) error {
|
|||
return atomic.WriteAll(batch, sharedBatch)
|
||||
}
|
||||
|
||||
func (vm *VM) newImportTx(nonce uint64, genesisData []byte, vmID ids.ID, fxIDs []ids.ID, chainName string, networkID uint32, key *crypto.PrivateKeySECP256K1R) (*ImportTx, error) {
|
||||
tx := &CreateChainTx{
|
||||
UnsignedCreateChainTx: UnsignedCreateChainTx{
|
||||
NetworkID: networkID,
|
||||
Nonce: nonce,
|
||||
GenesisData: genesisData,
|
||||
VMID: vmID,
|
||||
FxIDs: fxIDs,
|
||||
ChainName: chainName,
|
||||
},
|
||||
}
|
||||
func (vm *VM) newImportTx(nonce uint64, networkID uint32, ins []*ava.TransferableInput, from [][]*crypto.PrivateKeySECP256K1R, to *crypto.PrivateKeySECP256K1R) (*ImportTx, error) {
|
||||
ava.SortTransferableInputsWithSigners(ins, from)
|
||||
|
||||
unsignedIntf := interface{}(&tx.UnsignedCreateChainTx)
|
||||
tx := &ImportTx{UnsignedImportTx: UnsignedImportTx{
|
||||
NetworkID: networkID,
|
||||
Nonce: nonce,
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig, err := key.Sign(unsignedBytes)
|
||||
hash := hashing.ComputeHash256(unsignedBytes)
|
||||
|
||||
for _, credKeys := range from {
|
||||
cred := &secp256k1fx.Credential{}
|
||||
for _, key := range credKeys {
|
||||
sig, err := key.SignHash(hash)
|
||||
if err != nil {
|
||||
return nil, 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)
|
||||
}
|
||||
|
||||
sig, err := key.SignHash(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestBuildGenesis(t *testing.T) {
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x07, 0x5b, 0xcd, 0x15,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x05, 0x01, 0x5c, 0xce, 0x6c, 0x55, 0xd6, 0xb5,
|
||||
0x0b, 0x01, 0x5c, 0xce, 0x6c, 0x55, 0xd6, 0xb5,
|
||||
0x09, 0x84, 0x5c, 0x8c, 0x4e, 0x30, 0xbe, 0xd9,
|
||||
0x8d, 0x39, 0x1a, 0xe7, 0xf0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x3a, 0xde, 0x68, 0xb1, 0x00, 0x00, 0x00,
|
||||
|
|
|
@ -135,8 +135,8 @@ func init() {
|
|||
Codec.RegisterType(&UnsignedImportTx{}),
|
||||
Codec.RegisterType(&ImportTx{}),
|
||||
|
||||
// Codec.RegisterType(&UnsignedExportTx{}),
|
||||
// Codec.RegisterType(&ExportTx{}),
|
||||
Codec.RegisterType(&UnsignedExportTx{}),
|
||||
Codec.RegisterType(&ExportTx{}),
|
||||
|
||||
Codec.RegisterType(&advanceTimeTx{}),
|
||||
Codec.RegisterType(&rewardValidatorTx{}),
|
||||
|
@ -158,7 +158,8 @@ type VM struct {
|
|||
// AVA asset ID
|
||||
AVA ids.ID
|
||||
|
||||
fx secp256k1fx.Fx
|
||||
fx secp256k1fx.Fx
|
||||
codec codec.Codec
|
||||
|
||||
// Used to create and use keys.
|
||||
factory crypto.FactorySECP256K1R
|
||||
|
@ -173,6 +174,7 @@ type VM struct {
|
|||
// Transactions that have not been put into blocks yet
|
||||
unissuedEvents *EventHeap
|
||||
unissuedDecisionTxs []DecisionTx
|
||||
unissuedAtomicTxs []AtomicTx
|
||||
|
||||
// This timer goes off when it is time for the next validator to add/leave the validator set
|
||||
// When it goes off resetTimer() is called, triggering creation of a new block
|
||||
|
@ -200,6 +202,12 @@ func (vm *VM) Initialize(
|
|||
return err
|
||||
}
|
||||
|
||||
vm.codec = codec.NewDefault()
|
||||
if err := vm.fx.Initialize(vm); err != nil {
|
||||
return err
|
||||
}
|
||||
vm.codec = Codec
|
||||
|
||||
// Register this VM's types with the database so we can get/put structs to/from it
|
||||
vm.registerDBTypes()
|
||||
|
||||
|
@ -357,6 +365,24 @@ func (vm *VM) BuildBlock() (snowman.Block, error) {
|
|||
return blk, vm.DB.Commit()
|
||||
}
|
||||
|
||||
// If there is a pending atomic tx, build a block with it
|
||||
if len(vm.unissuedAtomicTxs) > 0 {
|
||||
tx := vm.unissuedAtomicTxs[0]
|
||||
vm.unissuedAtomicTxs = vm.unissuedAtomicTxs[1:]
|
||||
blk, err := vm.newAtomicBlock(preferredID, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := blk.Verify(); err != nil {
|
||||
vm.resetTimer()
|
||||
return nil, err
|
||||
}
|
||||
if err := vm.State.PutBlock(vm.DB, blk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blk, vm.DB.Commit()
|
||||
}
|
||||
|
||||
// Get the preferred block (which we want to build off)
|
||||
preferred, err := vm.getBlock(preferredID)
|
||||
vm.Ctx.Log.AssertNoError(err)
|
||||
|
@ -521,9 +547,9 @@ func (vm *VM) CreateStaticHandlers() map[string]*common.HTTPHandler {
|
|||
// Check if there is a block ready to be added to consensus
|
||||
// If so, notify the consensus engine
|
||||
func (vm *VM) resetTimer() {
|
||||
// If there is a pending CreateChainTx, trigger building of a block
|
||||
// with that transaction
|
||||
if len(vm.unissuedDecisionTxs) > 0 {
|
||||
// If there is a pending transaction, trigger building of a block with that
|
||||
// transaction
|
||||
if len(vm.unissuedDecisionTxs) > 0 || len(vm.unissuedAtomicTxs) > 0 {
|
||||
vm.SnowmanVM.NotifyBlockReady()
|
||||
return
|
||||
}
|
||||
|
@ -705,3 +731,9 @@ func (vm *VM) updateValidators(subnetID ids.ID) error {
|
|||
validatorSet.Set(validators)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Codec ...
|
||||
func (vm *VM) Codec() codec.Codec { return vm.codec }
|
||||
|
||||
// Clock ...
|
||||
func (vm *VM) Clock() *timer.Clock { return &vm.clock }
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/gecko/chains/atomic"
|
||||
"github.com/ava-labs/gecko/database/memdb"
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/snow"
|
||||
|
@ -18,7 +19,10 @@ import (
|
|||
"github.com/ava-labs/gecko/snow/validators"
|
||||
"github.com/ava-labs/gecko/utils/crypto"
|
||||
"github.com/ava-labs/gecko/utils/formatting"
|
||||
"github.com/ava-labs/gecko/utils/logging"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/components/core"
|
||||
"github.com/ava-labs/gecko/vms/secp256k1fx"
|
||||
"github.com/ava-labs/gecko/vms/timestampvm"
|
||||
)
|
||||
|
||||
|
@ -1058,5 +1062,169 @@ func TestCreateSubnet(t *testing.T) {
|
|||
if currentValidators.Len() != 0 {
|
||||
t.Fatal("pending validator set should be empty")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// test asset import
|
||||
func TestAtomicImport(t *testing.T) {
|
||||
vm := defaultVM()
|
||||
|
||||
utxoID := ava.UTXOID{
|
||||
TxID: ids.Empty.Prefix(0),
|
||||
OutputIndex: 1,
|
||||
}
|
||||
assetID := ids.Empty.Prefix(1)
|
||||
amount := uint64(50000)
|
||||
key := keys[0]
|
||||
|
||||
sm := &atomic.SharedMemory{}
|
||||
sm.Initialize(logging.NoLog{}, memdb.New())
|
||||
|
||||
vm.Ctx.SharedMemory = sm.NewBlockchainSharedMemory(vm.Ctx.ChainID)
|
||||
|
||||
tx, err := vm.newImportTx(
|
||||
defaultNonce+1,
|
||||
testNetworkID,
|
||||
[]*ava.TransferableInput{&ava.TransferableInput{
|
||||
UTXOID: utxoID,
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: amount,
|
||||
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
|
||||
},
|
||||
}},
|
||||
[][]*crypto.PrivateKeySECP256K1R{[]*crypto.PrivateKeySECP256K1R{key}},
|
||||
key,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vm.Ctx.Lock.Lock()
|
||||
defer vm.Ctx.Lock.Unlock()
|
||||
|
||||
vm.AVA = assetID
|
||||
|
||||
vm.unissuedAtomicTxs = append(vm.unissuedAtomicTxs, tx)
|
||||
if _, err := vm.BuildBlock(); err == nil {
|
||||
t.Fatalf("should have errored due to missing utxos")
|
||||
}
|
||||
|
||||
// Provide the avm UTXO:
|
||||
|
||||
bID := ids.Empty // TODO: Needs to be set to the platform chain
|
||||
smDB := vm.Ctx.SharedMemory.GetDatabase(bID)
|
||||
|
||||
utxo := &ava.UTXO{
|
||||
UTXOID: utxoID,
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: amount,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{key.PublicKey().Address()},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
state := ava.NewPrefixedState(smDB, Codec)
|
||||
if err := state.SetAVMUTXO(utxoID.InputID(), utxo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vm.Ctx.SharedMemory.ReleaseDatabase(bID)
|
||||
|
||||
vm.unissuedAtomicTxs = append(vm.unissuedAtomicTxs, tx)
|
||||
blk, err := vm.BuildBlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := blk.Verify(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
blk.Accept()
|
||||
|
||||
smDB = vm.Ctx.SharedMemory.GetDatabase(bID)
|
||||
defer vm.Ctx.SharedMemory.ReleaseDatabase(bID)
|
||||
|
||||
state = ava.NewPrefixedState(smDB, vm.codec)
|
||||
if _, err := state.AVMUTXO(utxoID.InputID()); err == nil {
|
||||
t.Fatalf("shouldn't have been able to read the utxo")
|
||||
}
|
||||
}
|
||||
|
||||
// test optimistic asset import
|
||||
func TestOptimisticAtomicImport(t *testing.T) {
|
||||
vm := defaultVM()
|
||||
|
||||
utxoID := ava.UTXOID{
|
||||
TxID: ids.Empty.Prefix(0),
|
||||
OutputIndex: 1,
|
||||
}
|
||||
assetID := ids.Empty.Prefix(1)
|
||||
amount := uint64(50000)
|
||||
key := keys[0]
|
||||
|
||||
sm := &atomic.SharedMemory{}
|
||||
sm.Initialize(logging.NoLog{}, memdb.New())
|
||||
|
||||
vm.Ctx.SharedMemory = sm.NewBlockchainSharedMemory(vm.Ctx.ChainID)
|
||||
|
||||
tx, err := vm.newImportTx(
|
||||
defaultNonce+1,
|
||||
testNetworkID,
|
||||
[]*ava.TransferableInput{&ava.TransferableInput{
|
||||
UTXOID: utxoID,
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: amount,
|
||||
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
|
||||
},
|
||||
}},
|
||||
[][]*crypto.PrivateKeySECP256K1R{[]*crypto.PrivateKeySECP256K1R{key}},
|
||||
key,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vm.Ctx.Lock.Lock()
|
||||
defer vm.Ctx.Lock.Unlock()
|
||||
|
||||
vm.AVA = assetID
|
||||
|
||||
blk, err := vm.newAtomicBlock(vm.Preferred(), tx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := blk.Verify(); err == nil {
|
||||
t.Fatalf("should have errored due to an invalid atomic utxo")
|
||||
}
|
||||
|
||||
previousAccount, err := vm.getAccount(vm.DB, key.PublicKey().Address())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
blk.Accept()
|
||||
|
||||
newAccount, err := vm.getAccount(vm.DB, key.PublicKey().Address())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if newAccount.Balance != previousAccount.Balance+amount {
|
||||
t.Fatalf("failed to provide funds")
|
||||
}
|
||||
|
||||
bID := ids.Empty // TODO: Needs to be set to the platform chain
|
||||
smDB := vm.Ctx.SharedMemory.GetDatabase(bID)
|
||||
defer vm.Ctx.SharedMemory.ReleaseDatabase(bID)
|
||||
|
||||
state := ava.NewPrefixedState(smDB, Codec)
|
||||
if _, err := state.AVMStatus(utxoID.InputID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue