diff --git a/genesis/genesis.go b/genesis/genesis.go index fa34a75..cdf0860 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -504,6 +504,9 @@ func VMGenesis(networkID uint32, vmID ids.ID) *platformvm.CreateChainTx { genesisBytes := Genesis(networkID) genesis := platformvm.Genesis{} platformvm.Codec.Unmarshal(genesisBytes, &genesis) + if err := genesis.Initialize(); err != nil { + panic(err) + } for _, chain := range genesis.Chains { if chain.VMID.Equals(vmID) { return chain diff --git a/vms/avm/vm.go b/vms/avm/vm.go index ce29921..1b7bd90 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -251,7 +251,10 @@ func (vm *VM) GetTx(txID ids.ID) (snowstorm.Tx, error) { ****************************************************************************** */ -// IssueTx attempts to send a transaction to consensus +// IssueTx attempts to send a transaction to consensus. +// If onDecide is specified, the function will be called when the transaction is +// either accepted or rejected with the appropriate status. This function will +// go out of scope when the transaction is removed from memory. func (vm *VM) IssueTx(b []byte, onDecide func(choices.Status)) (ids.ID, error) { tx, err := vm.parseTx(b) if err != nil { diff --git a/vms/avm/vm_test.go b/vms/avm/vm_test.go index 39c3ca6..715a8be 100644 --- a/vms/avm/vm_test.go +++ b/vms/avm/vm_test.go @@ -548,6 +548,8 @@ func TestGenesisGetUTXOs(t *testing.T) { } } +// Test issuing a transaction that consumes a currently pending UTXO. The +// transaction should be issued successfully. func TestIssueDependentTx(t *testing.T) { genesisBytes := BuildGenesisTest(t) diff --git a/vms/spchainvm/key_chain.go b/vms/spchainvm/keychain.go similarity index 77% rename from vms/spchainvm/key_chain.go rename to vms/spchainvm/keychain.go index 1984d8f..00eee64 100644 --- a/vms/spchainvm/key_chain.go +++ b/vms/spchainvm/keychain.go @@ -20,31 +20,42 @@ var ( // Keychain is a collection of keys that can be used to spend utxos type Keychain struct { + factory crypto.FactorySECP256K1R networkID uint32 chainID ids.ID - // This can be used to iterate over. However, it should not be modified externally. + + // Key: The id of a private key (namely, [privKey].PublicKey().Address().Key()) + // Value: The index in Keys of that private key keyMap map[[20]byte]int - Addrs ids.ShortSet - Keys []*crypto.PrivateKeySECP256K1R + + // Each element is an address controlled by a key in [Keys] + // This can be used to iterate over. It should not be modified externally. + Addrs ids.ShortSet + + // List of keys this keychain manages + // This can be used to iterate over. It should not be modified externally. + Keys []*crypto.PrivateKeySECP256K1R } // NewKeychain creates a new keychain for a chain func NewKeychain(networkID uint32, chainID ids.ID) *Keychain { return &Keychain{ - chainID: chainID, - keyMap: make(map[[20]byte]int), + networkID: networkID, + chainID: chainID, + keyMap: make(map[[20]byte]int), } } // New returns a newly generated private key -func (kc *Keychain) New() *crypto.PrivateKeySECP256K1R { - factory := &crypto.FactorySECP256K1R{} - - skGen, _ := factory.NewPrivateKey() +func (kc *Keychain) New() (*crypto.PrivateKeySECP256K1R, error) { + skGen, err := kc.factory.NewPrivateKey() + if err != nil { + return nil, err + } sk := skGen.(*crypto.PrivateKeySECP256K1R) kc.Add(sk) - return sk + return sk, nil } // Add a new key to the key chain diff --git a/vms/spdagvm/keychain.go b/vms/spdagvm/keychain.go index 1c4b8a2..7142a9d 100644 --- a/vms/spdagvm/keychain.go +++ b/vms/spdagvm/keychain.go @@ -20,29 +20,35 @@ var ( // Keychain is a collection of keys that can be used to spend utxos type Keychain struct { - // This can be used to iterate over. However, it should not be modified externally. + factory crypto.FactorySECP256K1R + networkID uint32 + chainID ids.ID + // Key: The id of a private key (namely, [privKey].PublicKey().Address().Key()) // Value: The index in Keys of that private key keyMap map[[20]byte]int // Each element is an address controlled by a key in [Keys] + // This can be used to iterate over. It should not be modified externally. Addrs ids.ShortSet // List of keys this keychain manages + // This can be used to iterate over. It should not be modified externally. Keys []*crypto.PrivateKeySECP256K1R } -func (kc *Keychain) init() { - if kc.keyMap == nil { - kc.keyMap = make(map[[20]byte]int) +// NewKeychain creates a new keychain for a chain +func NewKeychain(networkID uint32, chainID ids.ID) *Keychain { + return &Keychain{ + networkID: networkID, + chainID: chainID, + keyMap: make(map[[20]byte]int), } } // Add a new key to the key chain. // If [key] is already in the keychain, does nothing. func (kc *Keychain) Add(key *crypto.PrivateKeySECP256K1R) { - kc.init() - addr := key.PublicKey().Address() // The address controlled by [key] addrHash := addr.Key() if _, ok := kc.keyMap[addrHash]; !ok { @@ -53,9 +59,7 @@ func (kc *Keychain) Add(key *crypto.PrivateKeySECP256K1R) { } // Get a key from the keychain. If the key is unknown, the second return value is false. -func (kc Keychain) Get(id ids.ShortID) (*crypto.PrivateKeySECP256K1R, bool) { - kc.init() - +func (kc *Keychain) Get(id ids.ShortID) (*crypto.PrivateKeySECP256K1R, bool) { if i, ok := kc.keyMap[id.Key()]; ok { return kc.Keys[i], true } @@ -63,15 +67,13 @@ func (kc Keychain) Get(id ids.ShortID) (*crypto.PrivateKeySECP256K1R, bool) { } // Addresses returns a list of addresses this keychain manages -func (kc Keychain) Addresses() ids.ShortSet { return kc.Addrs } +func (kc *Keychain) Addresses() ids.ShortSet { return kc.Addrs } // New returns a newly generated private key. // The key and the address it controls are added to // [kc.Keys] and [kc.Addrs], respectively func (kc *Keychain) New() (*crypto.PrivateKeySECP256K1R, error) { - factory := crypto.FactorySECP256K1R{} - - skGen, err := factory.NewPrivateKey() + skGen, err := kc.factory.NewPrivateKey() if err != nil { return nil, err } @@ -84,8 +86,8 @@ func (kc *Keychain) New() (*crypto.PrivateKeySECP256K1R, error) { // Spend attempts to create an input func (kc *Keychain) Spend(utxo *UTXO, time uint64) (Input, *InputSigner, error) { builder := Builder{ - NetworkID: 0, - ChainID: ids.Empty, + NetworkID: kc.networkID, + ChainID: kc.chainID, } switch out := utxo.Out().(type) { @@ -148,8 +150,8 @@ func (kc *Keychain) GetSigsAndKeys(addresses []ids.ShortID, threshold int) ([]*S sigs := []*Sig{} keys := []*crypto.PrivateKeySECP256K1R{} builder := Builder{ - NetworkID: 0, - ChainID: ids.Empty, + NetworkID: kc.networkID, + ChainID: kc.chainID, } for i := uint32(0); i < uint32(len(addresses)) && len(keys) < threshold; i++ { if key, exists := kc.Get(addresses[i]); exists { diff --git a/vms/spdagvm/vm.go b/vms/spdagvm/vm.go index f5cd820..b873185 100644 --- a/vms/spdagvm/vm.go +++ b/vms/spdagvm/vm.go @@ -315,7 +315,7 @@ func (vm *VM) Send(amount uint64, assetID, toAddrStr string, fromPKs []string) ( } // Add all of the keys in [fromPKs] to a keychain - keychain := Keychain{} + keychain := NewKeychain(vm.ctx.NetworkID, vm.ctx.ChainID) factory := crypto.FactorySECP256K1R{} cb58 := formatting.CB58{} for _, fpk := range fromPKs { @@ -359,7 +359,7 @@ func (vm *VM) Send(amount uint64, assetID, toAddrStr string, fromPKs []string) ( ChainID: vm.ctx.ChainID, } currentTime := vm.clock.Unix() - tx, err := builder.NewTxFromUTXOs(&keychain, utxos, amount, vm.TxFee, 0, 1, toAddrs, outAddr, currentTime) + tx, err := builder.NewTxFromUTXOs(keychain, utxos, amount, vm.TxFee, 0, 1, toAddrs, outAddr, currentTime) if err != nil { return "", err } diff --git a/xputtest/avm.go b/xputtest/avm.go index 4da2c88..75cb498 100644 --- a/xputtest/avm.go +++ b/xputtest/avm.go @@ -19,12 +19,10 @@ import ( "github.com/ava-labs/gecko/xputtest/avmwallet" ) -func (n *network) benchmarkAVM(genesisState *platformvm.Genesis) { - avmChain := genesisState.Chains[0] - n.log.AssertTrue(avmChain.ChainName == "AVM", "wrong chain name") - genesisBytes := avmChain.GenesisData - - wallet, err := avmwallet.NewWallet(n.networkID, avmChain.ID(), config.AvaTxFee) +// benchmark an instance of the avm +func (n *network) benchmarkAVM(chain *platformvm.CreateChainTx) { + genesisBytes := chain.GenesisData + wallet, err := avmwallet.NewWallet(n.networkID, chain.ID(), config.AvaTxFee) n.log.AssertNoError(err) cb58 := formatting.CB58{} @@ -56,9 +54,10 @@ func (n *network) benchmarkAVM(genesisState *platformvm.Genesis) { n.log.AssertNoError(wallet.GenerateTxs(config.NumTxs, assetID)) - go n.log.RecoverAndPanic(func() { n.IssueAVM(avmChain.ID(), assetID, wallet) }) + go n.log.RecoverAndPanic(func() { n.IssueAVM(chain.ID(), assetID, wallet) }) } +// issue transactions to the instance of the avm funded by the provided wallet func (n *network) IssueAVM(chainID ids.ID, assetID ids.ID, wallet *avmwallet.Wallet) { n.log.Debug("Issuing with %d", wallet.Balance(assetID)) numAccepted := 0 diff --git a/xputtest/avmwallet/utxo_set.go b/xputtest/avmwallet/utxo_set.go index 2e3e49e..8f2cb69 100644 --- a/xputtest/avmwallet/utxo_set.go +++ b/xputtest/avmwallet/utxo_set.go @@ -13,9 +13,13 @@ import ( // UTXOSet ... type UTXOSet struct { - // This can be used to iterate over. However, it should not be modified externally. + // Key: The id of a UTXO + // Value: The index in UTXOs of that UTXO utxoMap map[[32]byte]int - UTXOs []*avm.UTXO + + // List of UTXOs in this set + // This can be used to iterate over. It should not be modified externally. + UTXOs []*avm.UTXO } // Put ... diff --git a/xputtest/avmwallet/wallet_test.go b/xputtest/avmwallet/wallet_test.go index b237a04..52ac797 100644 --- a/xputtest/avmwallet/wallet_test.go +++ b/xputtest/avmwallet/wallet_test.go @@ -13,7 +13,6 @@ import ( "github.com/ava-labs/gecko/utils/formatting" "github.com/ava-labs/gecko/utils/units" "github.com/ava-labs/gecko/vms/avm" - "github.com/ava-labs/gecko/vms/platformvm" "github.com/ava-labs/gecko/vms/secp256k1fx" ) @@ -245,20 +244,7 @@ func TestWalletWithGenesis(t *testing.T) { w.ImportKey(sk.(*crypto.PrivateKeySECP256K1R)) } - platformGenesisBytes := genesis.Genesis(genesis.LocalID) - genesisState := &platformvm.Genesis{} - err = platformvm.Codec.Unmarshal(platformGenesisBytes, genesisState) - if err != nil { - t.Fatal(err) - } - if err := genesisState.Initialize(); err != nil { - t.Fatal(err) - } - - avmChain := genesisState.Chains[0] - if name := avmChain.ChainName; name != "AVM" { - t.Fatalf("wrong chain name") - } + avmChain := genesis.VMGenesis(ctx.NetworkID, avm.ID) genesisBytes := avmChain.GenesisData genesis := avm.Genesis{} @@ -282,8 +268,8 @@ func TestWalletWithGenesis(t *testing.T) { assetID := genesisTx.ID() - if balance := w.Balance(assetID); balance != 45*units.MegaAva { - t.Fatalf("balance of %d was expected but got %d", 45*units.MegaAva, balance) + if balance := w.Balance(assetID); balance == 0 { + t.Fatalf("expected a positive balance") } for i := 1; i <= 1000; i++ { diff --git a/xputtest/chainwallet/wallet.go b/xputtest/chainwallet/wallet.go index f52f34f..a96ff2e 100644 --- a/xputtest/chainwallet/wallet.go +++ b/xputtest/chainwallet/wallet.go @@ -25,8 +25,8 @@ type Wallet struct { } // NewWallet ... -func NewWallet(networkID uint32, chainID ids.ID) Wallet { - return Wallet{ +func NewWallet(networkID uint32, chainID ids.ID) *Wallet { + return &Wallet{ networkID: networkID, chainID: chainID, keychain: spchainvm.NewKeychain(networkID, chainID), @@ -35,7 +35,13 @@ func NewWallet(networkID uint32, chainID ids.ID) Wallet { } // CreateAddress returns a brand new address! Ready to receive funds! -func (w *Wallet) CreateAddress() ids.ShortID { return w.keychain.New().PublicKey().Address() } +func (w *Wallet) CreateAddress() (ids.ShortID, error) { + sk, err := w.keychain.New() + if err != nil { + return ids.ShortID{}, err + } + return sk.PublicKey().Address(), nil +} // ImportKey imports a private key into this wallet func (w *Wallet) ImportKey(sk *crypto.PrivateKeySECP256K1R) { w.keychain.Add(sk) } @@ -61,60 +67,16 @@ func (w *Wallet) GenerateTxs(numTxs int) error { ctx.ChainID = w.chainID w.txs = make([]*spchainvm.Tx, numTxs) - for i := 0; i < numTxs; { - for _, account := range w.accountSet { - if i >= numTxs { - break - } - - accountID := account.ID() - key, exists := w.keychain.Get(accountID) - if !exists { - return errors.New("missing account") - } - - amount := uint64(1) - tx, sendAccount, err := account.CreateTx(amount, accountID, ctx, key) - if err != nil { - return err - } - - newAccount, err := sendAccount.Receive(tx, ctx) - if err != nil { - return err - } - w.accountSet[accountID.Key()] = newAccount - w.txs[i] = tx - i++ + for i := range w.txs { + tx, err := w.MakeTx() + if err != nil { + return err } + w.txs[i] = tx } return nil } -/* -// Send a new transaction -func (w *Wallet) Send() *spchainvm.Tx { - ctx := snow.DefaultContextTest() - ctx.NetworkID = w.networkID - ctx.ChainID = w.chainID - - for _, account := range w.accountSet { - accountID := account.ID() - if key, exists := w.keychain.Get(accountID); exists { - amount := uint64(1) - if tx, sendAccount, err := account.CreateTx(amount, accountID, ctx, key); err == nil { - newAccount, err := sendAccount.Receive(tx, ctx) - if err == nil { - w.accountSet[accountID.Key()] = newAccount - return tx - } - } - } - } - return nil -} -*/ - // NextTx returns the next tx to be sent as part of xput test func (w *Wallet) NextTx() *spchainvm.Tx { if len(w.txs) == 0 { @@ -125,6 +87,35 @@ func (w *Wallet) NextTx() *spchainvm.Tx { return tx } +// MakeTx creates a new transaction and update the state to after the tx is accepted +func (w *Wallet) MakeTx() (*spchainvm.Tx, error) { + ctx := snow.DefaultContextTest() + ctx.NetworkID = w.networkID + ctx.ChainID = w.chainID + + for _, account := range w.accountSet { + accountID := account.ID() + key, exists := w.keychain.Get(accountID) + if !exists { + return nil, errors.New("missing account") + } + + amount := uint64(1) + tx, sendAccount, err := account.CreateTx(amount, accountID, ctx, key) + if err != nil { + continue + } + + newAccount, err := sendAccount.Receive(tx, ctx) + if err != nil { + return nil, err + } + w.accountSet[accountID.Key()] = newAccount + return tx, nil + } + return nil, errors.New("empty") +} + func (w Wallet) String() string { return fmt.Sprintf( "Keychain:\n"+ diff --git a/xputtest/dagwallet/utxo_set.go b/xputtest/dagwallet/utxo_set.go index ef222f2..142a47c 100644 --- a/xputtest/dagwallet/utxo_set.go +++ b/xputtest/dagwallet/utxo_set.go @@ -13,9 +13,13 @@ import ( // UTXOSet ... type UTXOSet struct { - // This can be used to iterate over. However, it should not be modified externally. + // Key: The id of a UTXO + // Value: The index in UTXOs of that UTXO utxoMap map[[32]byte]int - UTXOs []*spdagvm.UTXO + + // List of UTXOs in this set + // This can be used to iterate over. It should not be modified externally. + UTXOs []*spdagvm.UTXO } // Put ... diff --git a/xputtest/dagwallet/wallet.go b/xputtest/dagwallet/wallet.go index edc2434..a10be8f 100644 --- a/xputtest/dagwallet/wallet.go +++ b/xputtest/dagwallet/wallet.go @@ -25,11 +25,11 @@ type Wallet struct { } // NewWallet returns a new Wallet -func NewWallet(networkID uint32, chainID ids.ID, txFee uint64) Wallet { - return Wallet{ +func NewWallet(networkID uint32, chainID ids.ID, txFee uint64) *Wallet { + return &Wallet{ networkID: networkID, chainID: chainID, - keychain: &spdagvm.Keychain{}, + keychain: spdagvm.NewKeychain(networkID, chainID), utxoSet: &UTXOSet{}, txFee: txFee, } diff --git a/xputtest/main.go b/xputtest/main.go index 28e7544..998d040 100644 --- a/xputtest/main.go +++ b/xputtest/main.go @@ -16,7 +16,9 @@ import ( "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/logging" - "github.com/ava-labs/gecko/vms/platformvm" + "github.com/ava-labs/gecko/vms/avm" + "github.com/ava-labs/gecko/vms/spchainvm" + "github.com/ava-labs/gecko/vms/spdagvm" ) func main() { @@ -24,6 +26,7 @@ func main() { fmt.Printf("Failed to parse arguments: %s\n", err) } + // set up logging config.LoggingConfig.Directory = path.Join(config.LoggingConfig.Directory, "client") log, err := logging.New(config.LoggingConfig) if err != nil { @@ -33,6 +36,7 @@ func main() { defer log.Stop() + // initialize state based on CLI args net.log = log crypto.EnableCrypto = config.EnableCrypto net.decided = make(chan ids.ID, config.MaxOutstandingTxs) @@ -42,11 +46,13 @@ func main() { return } + // Init the network log.AssertNoError(net.Initialize()) net.net.Start() defer net.net.Stop() + // connect to the node serr := salticidae.NewError() remoteIP := salticidae.NewNetAddrFromIPPortString(config.RemoteIP.String(), true, &serr) if code := serr.GetCode(); code != 0 { @@ -60,6 +66,7 @@ func main() { return } + // start a cpu profile file, gErr := os.Create("cpu_client.profile") log.AssertNoError(gErr) gErr = pprof.StartCPUProfile(file) @@ -71,22 +78,19 @@ func main() { net.networkID = config.NetworkID - platformGenesisBytes := genesis.Genesis(net.networkID) - genesisState := &platformvm.Genesis{} - log.AssertNoError(platformvm.Codec.Unmarshal(platformGenesisBytes, genesisState)) - log.AssertNoError(genesisState.Initialize()) - + // start the benchmark we want to run switch config.Chain { case spChain: - net.benchmarkSPChain(genesisState) + net.benchmarkSPChain(genesis.VMGenesis(config.NetworkID, spchainvm.ID)) case spDAG: - net.benchmarkSPDAG(genesisState) + net.benchmarkSPDAG(genesis.VMGenesis(config.NetworkID, spdagvm.ID)) case avmDAG: - net.benchmarkAVM(genesisState) + net.benchmarkAVM(genesis.VMGenesis(config.NetworkID, avm.ID)) default: log.Fatal("did not specify whether to test dag or chain. Exiting") return } + // start processing network messages net.ec.Dispatch() } diff --git a/xputtest/spchain.go b/xputtest/spchain.go index 2b92353..70f6947 100644 --- a/xputtest/spchain.go +++ b/xputtest/spchain.go @@ -19,12 +19,10 @@ import ( "github.com/ava-labs/gecko/xputtest/chainwallet" ) -func (n *network) benchmarkSPChain(genesisState *platformvm.Genesis) { - spchainChain := genesisState.Chains[3] - n.log.AssertTrue(spchainChain.ChainName == "Simple Chain Payments", "wrong chain name") - genesisBytes := spchainChain.GenesisData - - wallet := chainwallet.NewWallet(n.networkID, spchainChain.ID()) +// benchmark an instance of the sp chain +func (n *network) benchmarkSPChain(chain *platformvm.CreateChainTx) { + genesisBytes := chain.GenesisData + wallet := chainwallet.NewWallet(n.networkID, chain.ID()) codec := spchainvm.Codec{} accounts, err := codec.UnmarshalGenesis(genesisBytes) @@ -47,10 +45,10 @@ func (n *network) benchmarkSPChain(genesisState *platformvm.Genesis) { n.log.AssertNoError(wallet.GenerateTxs(config.NumTxs)) - go n.log.RecoverAndPanic(func() { n.IssueSPChain(spchainChain.ID(), wallet) }) + go n.log.RecoverAndPanic(func() { n.IssueSPChain(chain.ID(), wallet) }) } -func (n *network) IssueSPChain(chainID ids.ID, wallet chainwallet.Wallet) { +func (n *network) IssueSPChain(chainID ids.ID, wallet *chainwallet.Wallet) { n.log.Debug("Issuing with %d", wallet.Balance()) numAccepted := 0 numPending := 0 diff --git a/xputtest/spdag.go b/xputtest/spdag.go index db96b8b..edd2b95 100644 --- a/xputtest/spdag.go +++ b/xputtest/spdag.go @@ -19,12 +19,10 @@ import ( "github.com/ava-labs/gecko/xputtest/dagwallet" ) -func (n *network) benchmarkSPDAG(genesisState *platformvm.Genesis) { - spDAGChain := genesisState.Chains[2] - n.log.AssertTrue(spDAGChain.ChainName == "Simple DAG Payments", "wrong chain name") - genesisBytes := spDAGChain.GenesisData - - wallet := dagwallet.NewWallet(n.networkID, spDAGChain.ID(), config.AvaTxFee) +// benchmark an instance of the sp dag +func (n *network) benchmarkSPDAG(chain *platformvm.CreateChainTx) { + genesisBytes := chain.GenesisData + wallet := dagwallet.NewWallet(n.networkID, chain.ID(), config.AvaTxFee) codec := spdagvm.Codec{} tx, err := codec.UnmarshalTx(genesisBytes) @@ -43,10 +41,11 @@ func (n *network) benchmarkSPDAG(genesisState *platformvm.Genesis) { wallet.AddUTXO(utxo) } - go n.log.RecoverAndPanic(func() { n.IssueSPDAG(spDAGChain.ID(), wallet) }) + go n.log.RecoverAndPanic(func() { n.IssueSPDAG(chain.ID(), wallet) }) } -func (n *network) IssueSPDAG(chainID ids.ID, wallet dagwallet.Wallet) { +// issue transactions to the instance of the spdag funded by the provided wallet +func (n *network) IssueSPDAG(chainID ids.ID, wallet *dagwallet.Wallet) { n.log.Info("starting avalanche benchmark") pending := make(map[[32]byte]*spdagvm.Tx) canAdd := []*spdagvm.Tx{}