finished first pass of atomic swaps

This commit is contained in:
StephenButtolph 2020-03-26 11:27:47 -04:00
parent 93ed25f878
commit 3efccbf354
9 changed files with 503 additions and 239 deletions

View File

@ -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"`

View File

@ -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})
}

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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,

View File

@ -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 }

View File

@ -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)
}
}