- Added support for xput tests on the AVM
- Implemented an AVM wallet for throughput tests.
- Fixed credential bug in the AVM for transactions that depend on un-confirmed UTXOs.
This commit is contained in:
Stephen Buttolph 2020-03-13 17:31:23 -04:00 committed by GitHub
parent 094651b38d
commit 00df659961
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1437 additions and 402 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils/logging"
)
type issuableVM interface {
@ -17,17 +18,18 @@ type issuableVM interface {
// Issuer manages all the chain transaction flushing.
type Issuer struct {
lock sync.Mutex
vms map[[32]byte]issuableVM
locks map[[32]byte]sync.Locker
lock sync.Mutex
log logging.Logger
vms map[[32]byte]issuableVM
locks map[[32]byte]sync.Locker
callbacks chan func()
}
// Initialize this flusher
func (i *Issuer) Initialize() {
func (i *Issuer) Initialize(log logging.Logger) {
i.lock.Lock()
defer i.lock.Unlock()
i.log = log
i.vms = make(map[[32]byte]issuableVM)
i.locks = make(map[[32]byte]sync.Locker)
i.callbacks = make(chan func(), 1000)
@ -64,8 +66,12 @@ func (i *Issuer) IssueTx(chainID ids.ID, tx []byte, finalized func(choices.Statu
lock.Lock()
defer lock.Unlock()
if vm, exists := i.vms[key]; exists {
vm.IssueTx(tx, finalized)
if _, err := vm.IssueTx(tx, finalized); err != nil {
i.log.Error("Issuing the tx returned with %s unexpectedly", err)
}
}
}
} else {
i.log.Warn("Attempted to issue a Tx to an unsupported chain %s", chainID)
}
}

View File

@ -219,7 +219,7 @@ func (n *Node) initConsensusNet() {
func (n *Node) initClients() {
n.Issuer = &xputtest.Issuer{}
n.Issuer.Initialize()
n.Issuer.Initialize(n.Log)
n.CClientAPI = &xputtest.CClientHandler
n.CClientAPI.Initialize(n.ClientNet, n.Issuer)

View File

@ -102,7 +102,7 @@ func (t *BaseTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error
return err
}
}
if !isSortedTransferableOutputs(t.Outs, c) {
if !IsSortedTransferableOutputs(t.Outs, c) {
return errOutputsNotSorted
}
@ -215,7 +215,7 @@ func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) erro
return errIncompatibleFx
}
if err := fx.VerifyTransfer(uTx, utxo.Out, in.In, cred); err != nil {
if err := fx.VerifyTransfer(uTx, utxo.Out, in.In, cred.Cred); err != nil {
return err
}
}

View File

@ -1472,7 +1472,7 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) {
t.Fatal(err)
}
txID, err := vm.IssueTx(b)
txID, err := vm.IssueTx(b, nil)
if err != nil {
t.Fatal(err)
}
@ -1639,7 +1639,7 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) {
t.Fatal(err)
}
txID, err := vm.IssueTx(b)
txID, err := vm.IssueTx(b, nil)
if err != nil {
t.Fatal(err)
}
@ -1820,7 +1820,7 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) {
t.Fatal(err)
}
txID, err := vm.IssueTx(b)
txID, err := vm.IssueTx(b, nil)
if err != nil {
t.Fatal(err)
}
@ -1985,7 +1985,7 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) {
t.Fatal(err)
}
txID, err := vm.IssueTx(b)
txID, err := vm.IssueTx(b, nil)
if err != nil {
t.Fatal(err)
}

View File

@ -56,7 +56,7 @@ type IssueTxReply struct {
func (service *Service) IssueTx(r *http.Request, args *IssueTxArgs, reply *IssueTxReply) error {
service.vm.ctx.Log.Verbo("IssueTx called with %s", args.Tx)
txID, err := service.vm.IssueTx(args.Tx.Bytes)
txID, err := service.vm.IssueTx(args.Tx.Bytes, nil)
if err != nil {
return err
}
@ -303,7 +303,7 @@ func (service *Service) CreateFixedCapAsset(r *http.Request, args *CreateFixedCa
return fmt.Errorf("problem creating transaction: %w", err)
}
assetID, err := service.vm.IssueTx(b)
assetID, err := service.vm.IssueTx(b, nil)
if err != nil {
return fmt.Errorf("problem issuing transaction: %w", err)
}
@ -391,7 +391,7 @@ func (service *Service) CreateVariableCapAsset(r *http.Request, args *CreateVari
return fmt.Errorf("problem creating transaction: %w", err)
}
assetID, err := service.vm.IssueTx(b)
assetID, err := service.vm.IssueTx(b, nil)
if err != nil {
return fmt.Errorf("problem issuing transaction: %w", err)
}
@ -634,7 +634,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
return errInsufficientFunds
}
sortTransferableInputsWithSigners(ins, keys)
SortTransferableInputsWithSigners(ins, keys)
outs := []*TransferableOutput{
&TransferableOutput{
@ -671,7 +671,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
)
}
sortTransferableOutputs(outs, service.vm.codec)
SortTransferableOutputs(outs, service.vm.codec)
tx := Tx{
UnsignedTx: &BaseTx{
@ -708,7 +708,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
return fmt.Errorf("problem creating transaction: %w", err)
}
txID, err := service.vm.IssueTx(b)
txID, err := service.vm.IssueTx(b, nil)
if err != nil {
return fmt.Errorf("problem issuing transaction: %w", err)
}
@ -741,10 +741,15 @@ func (ins *innerSortTransferableInputsWithSigners) Swap(i, j int) {
ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j]
}
func sortTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) {
// 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})
}
func isSortedAndUniqueTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) bool {
// 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

@ -75,10 +75,13 @@ func (outs *innerSortTransferableOutputs) Less(i, j int) bool {
func (outs *innerSortTransferableOutputs) Len() int { return len(outs.outs) }
func (outs *innerSortTransferableOutputs) Swap(i, j int) { o := outs.outs; o[j], o[i] = o[i], o[j] }
func sortTransferableOutputs(outs []*TransferableOutput, c codec.Codec) {
// SortTransferableOutputs sorts output objects
func SortTransferableOutputs(outs []*TransferableOutput, c codec.Codec) {
sort.Sort(&innerSortTransferableOutputs{outs: outs, codec: c})
}
func isSortedTransferableOutputs(outs []*TransferableOutput, c codec.Codec) bool {
// IsSortedTransferableOutputs returns true if output objects are sorted
func IsSortedTransferableOutputs(outs []*TransferableOutput, c codec.Codec) bool {
return sort.IsSorted(&innerSortTransferableOutputs{outs: outs, codec: c})
}

View File

@ -85,11 +85,11 @@ func TestTransferableOutputSorting(t *testing.T) {
},
}
if isSortedTransferableOutputs(outs, c) {
if IsSortedTransferableOutputs(outs, c) {
t.Fatalf("Shouldn't be sorted")
}
sortTransferableOutputs(outs, c)
if !isSortedTransferableOutputs(outs, c) {
SortTransferableOutputs(outs, c)
if !IsSortedTransferableOutputs(outs, c) {
t.Fatalf("Should be sorted")
}
if result := outs[0].Out.(*TestTransferable).Val; result != 0 {

View File

@ -37,6 +37,8 @@ type txState struct {
deps []snowstorm.Tx
status choices.Status
onDecide func(choices.Status)
}
func (tx *UniqueTx) refresh() {
@ -124,6 +126,10 @@ func (tx *UniqueTx) Accept() {
tx.vm.pubsub.Publish("accepted", txID)
tx.t.deps = nil // Needed to prevent a memory leak
if tx.t.onDecide != nil {
tx.t.onDecide(choices.Accepted)
}
}
// Reject is called when the transaction was finalized as rejected by consensus
@ -143,6 +149,10 @@ func (tx *UniqueTx) Reject() {
tx.vm.pubsub.Publish("rejected", txID)
tx.t.deps = nil // Needed to prevent a memory leak
if tx.t.onDecide != nil {
tx.t.onDecide(choices.Rejected)
}
}
// Status returns the current status of this transaction

View File

@ -252,7 +252,7 @@ func (vm *VM) GetTx(txID ids.ID) (snowstorm.Tx, error) {
*/
// IssueTx attempts to send a transaction to consensus
func (vm *VM) IssueTx(b []byte) (ids.ID, error) {
func (vm *VM) IssueTx(b []byte, onDecide func(choices.Status)) (ids.ID, error) {
tx, err := vm.parseTx(b)
if err != nil {
return ids.ID{}, err
@ -261,6 +261,7 @@ func (vm *VM) IssueTx(b []byte) (ids.ID, error) {
return ids.ID{}, err
}
vm.issueTx(tx)
tx.t.onDecide = onDecide
return tx.ID(), nil
}

View File

@ -492,7 +492,7 @@ func TestIssueTx(t *testing.T) {
}
newTx.Initialize(b)
txID, err := vm.IssueTx(newTx.Bytes())
txID, err := vm.IssueTx(newTx.Bytes(), nil)
if err != nil {
t.Fatal(err)
}
@ -547,3 +547,159 @@ func TestGenesisGetUTXOs(t *testing.T) {
t.Fatalf("Wrong number of utxos (%d) returned", len(utxos))
}
}
func TestIssueDependentTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t)
issuer := make(chan common.Message, 1)
ctx.Lock.Lock()
vm := &VM{}
err := vm.Initialize(
ctx,
memdb.New(),
genesisBytes,
issuer,
[]*common.Fx{&common.Fx{
ID: ids.Empty,
Fx: &secp256k1fx.Fx{},
}},
)
if err != nil {
t.Fatal(err)
}
vm.batchTimeout = 0
genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t)
key := keys[0]
firstTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
TxID: genesisTx.ID(),
OutputIndex: 1,
},
Asset: Asset{ID: genesisTx.ID()},
In: &secp256k1fx.TransferInput{
Amt: 50000,
Input: secp256k1fx.Input{
SigIndices: []uint32{
0,
},
},
},
},
},
Outs: []*TransferableOutput{
&TransferableOutput{
Asset: Asset{ID: genesisTx.ID()},
Out: &secp256k1fx.TransferOutput{
Amt: 50000,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{key.PublicKey().Address()},
},
},
},
},
}}}
unsignedBytes, err := vm.codec.Marshal(&firstTx.UnsignedTx)
if err != nil {
t.Fatal(err)
}
sig, err := key.Sign(unsignedBytes)
if err != nil {
t.Fatal(err)
}
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
firstTx.Creds = append(firstTx.Creds, &Credential{
Cred: &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
},
})
b, err := vm.codec.Marshal(firstTx)
if err != nil {
t.Fatal(err)
}
firstTx.Initialize(b)
_, err = vm.IssueTx(firstTx.Bytes(), nil)
if err != nil {
t.Fatal(err)
}
secondTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
TxID: firstTx.ID(),
OutputIndex: 0,
},
Asset: Asset{ID: genesisTx.ID()},
In: &secp256k1fx.TransferInput{
Amt: 50000,
Input: secp256k1fx.Input{
SigIndices: []uint32{
0,
},
},
},
},
},
}}}
unsignedBytes, err = vm.codec.Marshal(&secondTx.UnsignedTx)
if err != nil {
t.Fatal(err)
}
sig, err = key.Sign(unsignedBytes)
if err != nil {
t.Fatal(err)
}
fixedSig = [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
secondTx.Creds = append(secondTx.Creds, &Credential{
Cred: &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
},
})
b, err = vm.codec.Marshal(secondTx)
if err != nil {
t.Fatal(err)
}
secondTx.Initialize(b)
_, err = vm.IssueTx(secondTx.Bytes(), nil)
if err != nil {
t.Fatal(err)
}
ctx.Lock.Unlock()
msg := <-issuer
if msg != common.PendingTxs {
t.Fatalf("Wrong message")
}
if txs := vm.PendingTxs(); len(txs) != 2 {
t.Fatalf("Should have returned %d tx(s)", 2)
}
}

View File

@ -18,8 +18,8 @@ var (
errUnknownAccount = errors.New("unknown account")
)
// KeyChain is a collection of keys that can be used to spend utxos
type KeyChain struct {
// Keychain is a collection of keys that can be used to spend utxos
type Keychain struct {
networkID uint32
chainID ids.ID
// This can be used to iterate over. However, it should not be modified externally.
@ -28,16 +28,16 @@ type KeyChain struct {
Keys []*crypto.PrivateKeySECP256K1R
}
// NewKeyChain creates a new keychain for a chain
func NewKeyChain(networkID uint32, chainID ids.ID) *KeyChain {
return &KeyChain{
// 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),
}
}
// New returns a newly generated private key
func (kc *KeyChain) New() *crypto.PrivateKeySECP256K1R {
func (kc *Keychain) New() *crypto.PrivateKeySECP256K1R {
factory := &crypto.FactorySECP256K1R{}
skGen, _ := factory.NewPrivateKey()
@ -48,7 +48,7 @@ func (kc *KeyChain) New() *crypto.PrivateKeySECP256K1R {
}
// Add a new key to the key chain
func (kc *KeyChain) Add(key *crypto.PrivateKeySECP256K1R) {
func (kc *Keychain) Add(key *crypto.PrivateKeySECP256K1R) {
addr := key.PublicKey().Address()
addrHash := addr.Key()
if _, ok := kc.keyMap[addrHash]; !ok {
@ -59,7 +59,7 @@ func (kc *KeyChain) Add(key *crypto.PrivateKeySECP256K1R) {
}
// Get a key from the keychain. If the key is unknown, the
func (kc *KeyChain) Get(id ids.ShortID) (*crypto.PrivateKeySECP256K1R, bool) {
func (kc *Keychain) Get(id ids.ShortID) (*crypto.PrivateKeySECP256K1R, bool) {
if i, ok := kc.keyMap[id.Key()]; ok {
return kc.Keys[i], true
}
@ -67,10 +67,10 @@ 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 }
// Spend attempts to create a new transaction
func (kc *KeyChain) Spend(account Account, amount uint64, destination ids.ShortID) (*Tx, Account, error) {
func (kc *Keychain) Spend(account Account, amount uint64, destination ids.ShortID) (*Tx, Account, error) {
key, exists := kc.Get(account.ID())
if !exists {
return nil, Account{}, errUnknownAccount
@ -83,7 +83,7 @@ func (kc *KeyChain) Spend(account Account, amount uint64, destination ids.ShortI
// PrefixedString returns a string representation of this keychain with each
// line prepended with [prefix]
func (kc *KeyChain) PrefixedString(prefix string) string {
func (kc *Keychain) PrefixedString(prefix string) string {
s := strings.Builder{}
format := fmt.Sprintf("%%sKey[%s]: Key: %%s Address: %%s\n",
@ -99,6 +99,6 @@ func (kc *KeyChain) PrefixedString(prefix string) string {
return strings.TrimSuffix(s.String(), "\n")
}
func (kc *KeyChain) String() string {
func (kc *Keychain) String() string {
return kc.PrefixedString("")
}

View File

@ -88,8 +88,8 @@ func (b Builder) NewSig(index uint32) *Sig { return &Sig{index: index} }
// * This output can't be spent until at least [locktime].
// * If there is any "change" there is another output controlled by [changeAddr] with the change.
// * The UTXOs consumed to make this transaction are a subset of [utxos].
// * The keys controlling [utxos] are in [keyChain]
func (b Builder) NewTxFromUTXOs(keyChain *KeyChain, utxos []*UTXO, amount, txFee, locktime uint64,
// * The keys controlling [utxos] are in [keychain]
func (b Builder) NewTxFromUTXOs(keychain *Keychain, utxos []*UTXO, amount, txFee, locktime uint64,
threshold uint32, toAddrs []ids.ShortID, changeAddr ids.ShortID, currentTime uint64) (*Tx, error) {
ins := []Input{} // Consumed by this transaction
@ -103,7 +103,7 @@ func (b Builder) NewTxFromUTXOs(keyChain *KeyChain, utxos []*UTXO, amount, txFee
spent := uint64(0) // The sum of the UTXOs consumed in this transaction
for i := 0; i < len(utxos) && amountPlusTxFee > spent; i++ {
utxo := utxos[i]
if in, signer, err := keyChain.Spend(utxo, currentTime); err == nil {
if in, signer, err := keychain.Spend(utxo, currentTime); err == nil {
ins = append(ins, in)
amount := in.(*InputPayment).Amount()
spent += amount

View File

@ -18,8 +18,8 @@ var (
errCantSpend = errors.New("utxo couldn't be spent")
)
// KeyChain is a collection of keys that can be used to spend utxos
type KeyChain struct {
// 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.
// Key: The id of a private key (namely, [privKey].PublicKey().Address().Key())
// Value: The index in Keys of that private key
@ -32,7 +32,7 @@ type KeyChain struct {
Keys []*crypto.PrivateKeySECP256K1R
}
func (kc *KeyChain) init() {
func (kc *Keychain) init() {
if kc.keyMap == nil {
kc.keyMap = make(map[[20]byte]int)
}
@ -40,7 +40,7 @@ func (kc *KeyChain) init() {
// Add a new key to the key chain.
// If [key] is already in the keychain, does nothing.
func (kc *KeyChain) Add(key *crypto.PrivateKeySECP256K1R) {
func (kc *Keychain) Add(key *crypto.PrivateKeySECP256K1R) {
kc.init()
addr := key.PublicKey().Address() // The address controlled by [key]
@ -53,7 +53,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) {
func (kc Keychain) Get(id ids.ShortID) (*crypto.PrivateKeySECP256K1R, bool) {
kc.init()
if i, ok := kc.keyMap[id.Key()]; ok {
@ -63,12 +63,12 @@ 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) {
func (kc *Keychain) New() (*crypto.PrivateKeySECP256K1R, error) {
factory := crypto.FactorySECP256K1R{}
skGen, err := factory.NewPrivateKey()
@ -82,7 +82,7 @@ func (kc *KeyChain) New() (*crypto.PrivateKeySECP256K1R, error) {
}
// Spend attempts to create an input
func (kc *KeyChain) Spend(utxo *UTXO, time uint64) (Input, *InputSigner, error) {
func (kc *Keychain) Spend(utxo *UTXO, time uint64) (Input, *InputSigner, error) {
builder := Builder{
NetworkID: 0,
ChainID: ids.Empty,
@ -144,7 +144,7 @@ func (kc *KeyChain) Spend(utxo *UTXO, time uint64) (Input, *InputSigner, error)
// 2) A list of private keys such that each key controls an address in [addresses]
// 3) true iff this keychain contains at least [threshold] keys that control an address
// in [addresses]
func (kc *KeyChain) GetSigsAndKeys(addresses []ids.ShortID, threshold int) ([]*Sig, []*crypto.PrivateKeySECP256K1R, bool) {
func (kc *Keychain) GetSigsAndKeys(addresses []ids.ShortID, threshold int) ([]*Sig, []*crypto.PrivateKeySECP256K1R, bool) {
sigs := []*Sig{}
keys := []*crypto.PrivateKeySECP256K1R{}
builder := Builder{
@ -162,7 +162,7 @@ func (kc *KeyChain) GetSigsAndKeys(addresses []ids.ShortID, threshold int) ([]*S
// PrefixedString returns the key chain as a string representation with [prefix]
// added before every line.
func (kc *KeyChain) PrefixedString(prefix string) string {
func (kc *Keychain) PrefixedString(prefix string) string {
s := strings.Builder{}
format := fmt.Sprintf("%%sKey[%s]: Key: %%s Address: %%s\n",
@ -178,4 +178,4 @@ func (kc *KeyChain) PrefixedString(prefix string) string {
return strings.TrimSuffix(s.String(), "\n")
}
func (kc *KeyChain) String() string { return kc.PrefixedString("") }
func (kc *Keychain) String() string { return kc.PrefixedString("") }

View File

@ -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 := Keychain{}
factory := crypto.FactorySECP256K1R{}
cb58 := formatting.CB58{}
for _, fpk := range fromPKs {

105
xputtest/avm.go Normal file
View File

@ -0,0 +1,105 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main
import (
"time"
"github.com/ava-labs/salticidae-go"
"github.com/ava-labs/gecko/genesis"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/networking"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/timer"
"github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/platformvm"
"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)
n.log.AssertNoError(err)
cb58 := formatting.CB58{}
keyStr := genesis.Keys[config.Key]
n.log.AssertNoError(cb58.FromString(keyStr))
factory := crypto.FactorySECP256K1R{}
sk, err := factory.ToPrivateKey(cb58.Bytes)
n.log.AssertNoError(err)
wallet.ImportKey(sk.(*crypto.PrivateKeySECP256K1R))
codec := wallet.Codec()
genesis := avm.Genesis{}
n.log.AssertNoError(codec.Unmarshal(genesisBytes, &genesis))
genesisTx := genesis.Txs[0]
tx := avm.Tx{
UnsignedTx: &genesisTx.CreateAssetTx,
}
txBytes, err := codec.Marshal(&tx)
n.log.AssertNoError(err)
tx.Initialize(txBytes)
for _, utxo := range tx.UTXOs() {
wallet.AddUTXO(utxo)
}
assetID := genesisTx.ID()
n.log.AssertNoError(wallet.GenerateTxs(config.NumTxs, assetID))
go n.log.RecoverAndPanic(func() { n.IssueAVM(avmChain.ID(), assetID, 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
numPending := 0
n.decided <- ids.ID{}
meter := timer.TimedMeter{Duration: time.Second}
for d := range n.decided {
if numAccepted%1000 == 0 {
n.log.Info("TPS: %d", meter.Ticks())
}
if !d.IsZero() {
meter.Tick()
n.log.Debug("Finalized %s", d)
numAccepted++
numPending--
}
for numPending < config.MaxOutstandingTxs && wallet.Balance(assetID) > 0 && numAccepted+numPending < config.NumTxs {
tx := wallet.NextTx()
n.log.AssertTrue(tx != nil, "Tx creation failed")
it, err := n.build.IssueTx(chainID, tx.Bytes())
n.log.AssertNoError(err)
ds := it.DataStream()
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
newMsg := salticidae.NewMsgMovedFromByteArray(networking.IssueTx, ba, false)
n.conn.GetNet().SendMsg(newMsg, n.conn)
ds.Free()
ba.Free()
newMsg.Free()
numPending++
n.log.Debug("Sent tx, pending = %d, accepted = %d", numPending, numAccepted)
}
if numAccepted+numPending >= config.NumTxs {
n.log.Info("done with test")
return
}
}
}

View File

@ -0,0 +1,88 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avmwallet
import (
"fmt"
"strings"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/avm"
)
// UTXOSet ...
type UTXOSet struct {
// This can be used to iterate over. However, it should not be modified externally.
utxoMap map[[32]byte]int
UTXOs []*avm.UTXO
}
// Put ...
func (us *UTXOSet) Put(utxo *avm.UTXO) {
if us.utxoMap == nil {
us.utxoMap = make(map[[32]byte]int)
}
utxoID := utxo.InputID()
utxoKey := utxoID.Key()
if _, ok := us.utxoMap[utxoKey]; !ok {
us.utxoMap[utxoKey] = len(us.UTXOs)
us.UTXOs = append(us.UTXOs, utxo)
}
}
// Get ...
func (us *UTXOSet) Get(id ids.ID) *avm.UTXO {
if us.utxoMap == nil {
return nil
}
if i, ok := us.utxoMap[id.Key()]; ok {
utxo := us.UTXOs[i]
return utxo
}
return nil
}
// Remove ...
func (us *UTXOSet) Remove(id ids.ID) *avm.UTXO {
i, ok := us.utxoMap[id.Key()]
if !ok {
return nil
}
utxoI := us.UTXOs[i]
j := len(us.UTXOs) - 1
utxoJ := us.UTXOs[j]
us.UTXOs[i] = us.UTXOs[j]
us.UTXOs = us.UTXOs[:j]
us.utxoMap[utxoJ.InputID().Key()] = i
delete(us.utxoMap, utxoI.InputID().Key())
return utxoI
}
// PrefixedString returns a string with each new line prefixed with [prefix]
func (us *UTXOSet) PrefixedString(prefix string) string {
s := strings.Builder{}
s.WriteString(fmt.Sprintf("UTXOs (length=%d):", len(us.UTXOs)))
for i, utxo := range us.UTXOs {
utxoID := utxo.InputID()
txID, txIndex := utxo.InputSource()
s.WriteString(fmt.Sprintf("\n%sUTXO[%d]:"+
"\n%s UTXOID: %s"+
"\n%s TxID: %s"+
"\n%s TxIndex: %d",
prefix, i,
prefix, utxoID,
prefix, txID,
prefix, txIndex))
}
return s.String()
}
func (us *UTXOSet) String() string { return us.PrefixedString(" ") }

View File

@ -0,0 +1,297 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avmwallet
import (
"errors"
"fmt"
stdmath "math"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"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/utils/timer"
"github.com/ava-labs/gecko/utils/wrappers"
"github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
// Wallet is a holder for keys and UTXOs for the Ava DAG.
type Wallet struct {
networkID uint32
chainID ids.ID
clock timer.Clock
codec codec.Codec
keychain *secp256k1fx.Keychain // Mapping from public address to the SigningKeys
utxoSet *UTXOSet // Mapping from utxoIDs to UTXOs
balance map[[32]byte]uint64
txFee uint64
txsSent int32
txs []*avm.Tx
}
// NewWallet returns a new Wallet
func NewWallet(networkID uint32, chainID ids.ID, txFee uint64) (*Wallet, error) {
c := codec.NewDefault()
errs := wrappers.Errs{}
errs.Add(
c.RegisterType(&avm.BaseTx{}),
c.RegisterType(&avm.CreateAssetTx{}),
c.RegisterType(&avm.OperationTx{}),
c.RegisterType(&secp256k1fx.MintOutput{}),
c.RegisterType(&secp256k1fx.TransferOutput{}),
c.RegisterType(&secp256k1fx.MintInput{}),
c.RegisterType(&secp256k1fx.TransferInput{}),
c.RegisterType(&secp256k1fx.Credential{}),
)
return &Wallet{
networkID: networkID,
chainID: chainID,
codec: c,
keychain: secp256k1fx.NewKeychain(),
utxoSet: &UTXOSet{},
balance: make(map[[32]byte]uint64),
txFee: txFee,
}, errs.Err
}
// Codec returns the codec used for serialization
func (w *Wallet) Codec() codec.Codec { return w.codec }
// GetAddress returns one of the addresses this wallet manages. If no address
// exists, one will be created.
func (w *Wallet) GetAddress() (ids.ShortID, error) {
if w.keychain.Addrs.Len() == 0 {
return w.CreateAddress()
}
return w.keychain.Addrs.CappedList(1)[0], nil
}
// CreateAddress returns a new address.
// It also saves the address and the private key that controls it
// so the address can be used later
func (w *Wallet) CreateAddress() (ids.ShortID, error) {
privKey, err := w.keychain.New()
return privKey.PublicKey().Address(), err
}
// ImportKey imports a private key into this wallet
func (w *Wallet) ImportKey(sk *crypto.PrivateKeySECP256K1R) { w.keychain.Add(sk) }
// AddUTXO adds a new UTXO to this wallet if this wallet may spend it
// The UTXO's output must be an OutputPayment
func (w *Wallet) AddUTXO(utxo *avm.UTXO) {
out, ok := utxo.Out.(avm.FxTransferable)
if !ok {
return
}
if _, _, err := w.keychain.Spend(out, stdmath.MaxUint64); err == nil {
w.utxoSet.Put(utxo)
w.balance[utxo.AssetID().Key()] += out.Amount()
}
}
// RemoveUTXO from this wallet
func (w *Wallet) RemoveUTXO(utxoID ids.ID) {
utxo := w.utxoSet.Get(utxoID)
if utxo == nil {
return
}
assetID := utxo.AssetID()
assetKey := assetID.Key()
newBalance := w.balance[assetKey] - utxo.Out.(avm.FxTransferable).Amount()
if newBalance == 0 {
delete(w.balance, assetKey)
} else {
w.balance[assetKey] = newBalance
}
w.utxoSet.Remove(utxoID)
}
// Balance returns the amount of the assets in this wallet
func (w *Wallet) Balance(assetID ids.ID) uint64 { return w.balance[assetID.Key()] }
// CreateTx returns a tx that sends [amount] of [assetID] to [destAddr]
func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) (*avm.Tx, error) {
if amount == 0 {
return nil, errors.New("invalid amount")
}
amountSpent := uint64(0)
time := w.clock.Unix()
ins := []*avm.TransferableInput{}
keys := [][]*crypto.PrivateKeySECP256K1R{}
for _, utxo := range w.utxoSet.UTXOs {
if !utxo.AssetID().Equals(assetID) {
continue
}
inputIntf, signers, err := w.keychain.Spend(utxo.Out, time)
if err != nil {
continue
}
input, ok := inputIntf.(avm.FxTransferable)
if !ok {
continue
}
spent, err := math.Add64(amountSpent, input.Amount())
if err != nil {
return nil, err
}
amountSpent = spent
in := &avm.TransferableInput{
UTXOID: utxo.UTXOID,
Asset: avm.Asset{ID: assetID},
In: input,
}
ins = append(ins, in)
keys = append(keys, signers)
if amountSpent >= amount {
break
}
}
if amountSpent < amount {
return nil, errors.New("insufficient funds")
}
avm.SortTransferableInputsWithSigners(ins, keys)
outs := []*avm.TransferableOutput{
&avm.TransferableOutput{
Asset: avm.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{
Amt: amount,
Locktime: 0,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{destAddr},
},
},
},
}
if amountSpent > amount {
changeAddr, err := w.GetAddress()
if err != nil {
return nil, err
}
outs = append(outs,
&avm.TransferableOutput{
Asset: avm.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{
Amt: amountSpent - amount,
Locktime: 0,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{changeAddr},
},
},
},
)
}
avm.SortTransferableOutputs(outs, w.codec)
tx := &avm.Tx{
UnsignedTx: &avm.BaseTx{
NetID: w.networkID,
BCID: w.chainID,
Outs: outs,
Ins: ins,
},
}
unsignedBytes, err := w.codec.Marshal(&tx.UnsignedTx)
if err != nil {
return nil, 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 nil, err
}
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
cred.Sigs = append(cred.Sigs, fixedSig)
}
tx.Creds = append(tx.Creds, &avm.Credential{Cred: cred})
}
b, err := w.codec.Marshal(tx)
if err != nil {
return nil, err
}
tx.Initialize(b)
return tx, nil
}
// GenerateTxs generates the transactions that will be sent
// during the test
// Generate them all on test initialization so tx generation is not bottleneck
// in testing
func (w *Wallet) GenerateTxs(numTxs int, assetID ids.ID) error {
ctx := snow.DefaultContextTest()
ctx.NetworkID = w.networkID
ctx.ChainID = w.chainID
w.txs = make([]*avm.Tx, numTxs)
for i := 0; i < numTxs; i++ {
addr, err := w.CreateAddress()
if err != nil {
return err
}
tx, err := w.CreateTx(assetID, 1, addr)
if err != nil {
return err
}
for _, utxoID := range tx.InputUTXOs() {
w.RemoveUTXO(utxoID.InputID())
}
for _, utxo := range tx.UTXOs() {
w.AddUTXO(utxo)
}
w.txs[i] = tx
}
return nil
}
// NextTx returns the next tx to be sent as part of xput test
func (w *Wallet) NextTx() *avm.Tx {
if len(w.txs) == 0 {
return nil
}
tx := w.txs[0]
w.txs = w.txs[1:]
return tx
}
func (w *Wallet) String() string {
return fmt.Sprintf(
"Keychain:\n"+
"%s\n"+
"%s",
w.keychain.PrefixedString(" "),
w.utxoSet.PrefixedString(" "),
)
}

View File

@ -0,0 +1,315 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avmwallet
import (
"testing"
"github.com/ava-labs/gecko/genesis"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/utils/crypto"
"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"
)
func TestNewWallet(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0)
if err != nil {
t.Fatal(err)
}
if w == nil {
t.Fatalf("failed to create the new wallet")
}
}
func TestWalletGetAddress(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0)
if err != nil {
t.Fatal(err)
}
addr0, err := w.GetAddress()
if err != nil {
t.Fatal(err)
}
if addr0.IsZero() || addr0.Equals(ids.ShortEmpty) {
t.Fatalf("expected new address but got %s", addr0)
}
}
func TestWalletGetMultipleAddresses(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0)
if err != nil {
t.Fatal(err)
}
addr0, err := w.GetAddress()
if err != nil {
t.Fatal(err)
}
addr1, err := w.GetAddress()
if err != nil {
t.Fatal(err)
}
if !addr0.Equals(addr1) {
t.Fatalf("Should have returned the same address from multiple Get Address calls")
}
}
func TestWalletEmptyBalance(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0)
if err != nil {
t.Fatal(err)
}
if balance := w.Balance(ids.Empty); balance != 0 {
t.Fatalf("expected balance to be 0, was %d", balance)
}
}
func TestWalletAddUTXO(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0)
if err != nil {
t.Fatal(err)
}
utxo := &avm.UTXO{
UTXOID: avm.UTXOID{TxID: ids.Empty.Prefix(0)},
Asset: avm.Asset{ID: ids.Empty.Prefix(1)},
Out: &secp256k1fx.TransferOutput{
Amt: 1000,
},
}
w.AddUTXO(utxo)
if balance := w.Balance(utxo.AssetID()); balance != 1000 {
t.Fatalf("expected balance to be 1000, was %d", balance)
}
}
func TestWalletAddInvalidUTXO(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0)
if err != nil {
t.Fatal(err)
}
utxo := &avm.UTXO{
UTXOID: avm.UTXOID{TxID: ids.Empty.Prefix(0)},
Asset: avm.Asset{ID: ids.Empty.Prefix(1)},
}
w.AddUTXO(utxo)
if balance := w.Balance(utxo.AssetID()); balance != 0 {
t.Fatalf("expected balance to be 0, was %d", balance)
}
}
func TestWalletCreateTx(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0)
if err != nil {
t.Fatal(err)
}
assetID := ids.Empty.Prefix(0)
addr, err := w.GetAddress()
if err != nil {
t.Fatal(err)
}
utxo := &avm.UTXO{
UTXOID: avm.UTXOID{TxID: ids.Empty.Prefix(1)},
Asset: avm.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{
Amt: 1000,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{addr},
},
},
}
w.AddUTXO(utxo)
destAddr, err := w.CreateAddress()
if err != nil {
t.Fatal(err)
}
tx, err := w.CreateTx(assetID, 1000, destAddr)
if err != nil {
t.Fatal(err)
}
if balance := w.Balance(utxo.AssetID()); balance != 1000 {
t.Fatalf("expected balance to be 1000, was %d", balance)
}
for _, utxo := range tx.InputUTXOs() {
w.RemoveUTXO(utxo.InputID())
}
if balance := w.Balance(utxo.AssetID()); balance != 0 {
t.Fatalf("expected balance to be 0, was %d", balance)
}
}
func TestWalletImportKey(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0)
if err != nil {
t.Fatal(err)
}
factory := crypto.FactorySECP256K1R{}
sk, err := factory.NewPrivateKey()
if err != nil {
t.Fatal(err)
}
w.ImportKey(sk.(*crypto.PrivateKeySECP256K1R))
addr0 := sk.PublicKey().Address()
addr1, err := w.GetAddress()
if err != nil {
t.Fatal(err)
}
if !addr0.Equals(addr1) {
t.Fatalf("Should have returned the same address from the Get Address call")
}
}
func TestWalletString(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0)
if err != nil {
t.Fatal(err)
}
skBytes := []byte{
0x4a, 0x99, 0x82, 0x98, 0x5c, 0x39, 0xa8, 0x04,
0x87, 0x4c, 0x62, 0x3c, 0xd4, 0x9e, 0xa7, 0x7d,
0x63, 0x5f, 0x92, 0x7c, 0xb9, 0x6b, 0x3f, 0xb7,
0x3b, 0x93, 0x59, 0xa2, 0x4f, 0xb4, 0x0c, 0x9e,
}
factory := crypto.FactorySECP256K1R{}
sk, err := factory.ToPrivateKey(skBytes)
if err != nil {
t.Fatal(err)
}
w.ImportKey(sk.(*crypto.PrivateKeySECP256K1R))
expected := "Keychain:" +
"\n Key[0]: Key: ZrYnAmArnk97JGzkq3kxTmFuKQnmajc86Xyd3JXC29meZ7znH Address: EHQiyKpq1VxkyNzt9bj1BLn5tzQ6Vt96q" +
"\nUTXOs (length=0):"
if str := w.String(); str != expected {
t.Fatalf("got:\n%s\n\nexpected:\n%s", str, expected)
}
}
func TestWalletWithGenesis(t *testing.T) {
ctx := snow.DefaultContextTest()
ctx.NetworkID = 12345
ctx.ChainID = ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(ctx.NetworkID, ctx.ChainID, 0)
if err != nil {
t.Fatal(err)
}
b58 := formatting.CB58{}
factory := crypto.FactorySECP256K1R{}
for _, key := range genesis.Keys {
if err := b58.FromString(key); err != nil {
t.Fatal(err)
}
sk, err := factory.ToPrivateKey(b58.Bytes)
if err != nil {
t.Fatal(err)
}
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")
}
genesisBytes := avmChain.GenesisData
genesis := avm.Genesis{}
if err := w.codec.Unmarshal(genesisBytes, &genesis); err != nil {
t.Fatal(err)
}
genesisTx := genesis.Txs[0]
tx := avm.Tx{
UnsignedTx: &genesisTx.CreateAssetTx,
}
txBytes, err := w.codec.Marshal(&tx)
if err != nil {
t.Fatal(err)
}
tx.Initialize(txBytes)
for _, utxo := range tx.UTXOs() {
w.AddUTXO(utxo)
}
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)
}
for i := 1; i <= 1000; i++ {
addr, err := w.CreateAddress()
if err != nil {
t.Fatal(err)
}
tx, err := w.CreateTx(assetID, uint64(i), addr)
if err != nil {
t.Fatal(err)
}
if err := tx.SyntacticVerify(ctx, w.codec, 1); err != nil {
t.Fatal(err)
}
for _, utxoID := range tx.InputUTXOs() {
w.RemoveUTXO(utxoID.InputID())
}
for _, utxo := range tx.UTXOs() {
w.AddUTXO(utxo)
}
if balance := w.Balance(assetID); balance != 45*units.MegaAva {
t.Fatalf("balance of %d was expected but got %d", 45*units.MegaAva, balance)
}
}
}

View File

@ -8,7 +8,8 @@ type ChainType int
// Chain types
const (
UnknownChain ChainType = iota
ChainChain
DagChain
unknown ChainType = iota
spChain
spDAG
avmDAG
)

View File

@ -4,6 +4,7 @@
package chainwallet
import (
"errors"
"fmt"
"github.com/ava-labs/gecko/ids"
@ -12,22 +13,15 @@ import (
"github.com/ava-labs/gecko/vms/spchainvm"
)
// The max number of transactions this wallet can send as part of the throughput tests
// lower --> low startup time but test has shorter duration
// higher --> high startup time but test has longer duration
const (
MaxNumTxs = 25000
)
// Wallet is a holder for keys and UTXOs.
type Wallet struct {
networkID uint32
chainID ids.ID
keyChain *spchainvm.KeyChain // Mapping from public address to the SigningKeys
keychain *spchainvm.Keychain // Mapping from public address to the SigningKeys
accountSet map[[20]byte]spchainvm.Account // Mapping from addresses to accounts
balance uint64
TxsSent int32
txs [MaxNumTxs]*spchainvm.Tx
txs []*spchainvm.Tx
}
// NewWallet ...
@ -35,16 +29,16 @@ func NewWallet(networkID uint32, chainID ids.ID) Wallet {
return Wallet{
networkID: networkID,
chainID: chainID,
keyChain: spchainvm.NewKeyChain(networkID, chainID),
keychain: spchainvm.NewKeychain(networkID, chainID),
accountSet: make(map[[20]byte]spchainvm.Account),
}
}
// 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 { return w.keychain.New().PublicKey().Address() }
// ImportKey imports a private key into this wallet
func (w *Wallet) ImportKey(sk *crypto.PrivateKeySECP256K1R) { w.keyChain.Add(sk) }
func (w *Wallet) ImportKey(sk *crypto.PrivateKeySECP256K1R) { w.keychain.Add(sk) }
// AddAccount adds a new account to this wallet, if this wallet can spend it.
func (w *Wallet) AddAccount(account spchainvm.Account) {
@ -61,35 +55,40 @@ func (w *Wallet) Balance() uint64 { return w.balance }
// during the test
// Generate them all on test initialization so tx generation is not bottleneck
// in testing
func (w *Wallet) GenerateTxs() {
func (w *Wallet) GenerateTxs(numTxs int) error {
ctx := snow.DefaultContextTest()
ctx.NetworkID = w.networkID
ctx.ChainID = w.chainID
for i := 0; i < MaxNumTxs; i++ {
if i%1000 == 0 {
fmt.Printf("generated %d transactions\n", i)
}
w.txs = make([]*spchainvm.Tx, numTxs)
for i := 0; i < numTxs; {
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 {
panic("shouldn't error")
}
w.accountSet[accountID.Key()] = newAccount
w.txs[i] = tx
continue
} else {
panic("shouldn't error here either: " + err.Error())
}
} else {
panic("shouldn't not exist")
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++
}
}
return nil
}
/*
@ -101,7 +100,7 @@ func (w *Wallet) Send() *spchainvm.Tx {
for _, account := range w.accountSet {
accountID := account.ID()
if key, exists := w.keyChain.Get(accountID); exists {
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)
@ -118,16 +117,17 @@ func (w *Wallet) Send() *spchainvm.Tx {
// NextTx returns the next tx to be sent as part of xput test
func (w *Wallet) NextTx() *spchainvm.Tx {
if w.TxsSent >= MaxNumTxs {
if len(w.txs) == 0 {
return nil
}
w.TxsSent++
return w.txs[w.TxsSent-1]
tx := w.txs[0]
w.txs = w.txs[1:]
return tx
}
func (w Wallet) String() string {
return fmt.Sprintf(
"KeyChain:\n"+
"Keychain:\n"+
"%s",
w.keyChain.PrefixedString(" "))
w.keychain.PrefixedString(" "))
}

View File

@ -23,7 +23,8 @@ type Config struct {
LoggingConfig logging.Config
// Key describes which key to use to issue transactions
// NumTxs describes the number of transactions to issue
// MaxOutstandingTxs describes how many txs to pipeline
Key, MaxOutstandingTxs int
Chain ChainType
Key, NumTxs, MaxOutstandingTxs int
Chain ChainType
}

View File

@ -11,49 +11,49 @@ import (
"github.com/ava-labs/gecko/vms/spdagvm"
)
// UtxoSet ...
type UtxoSet struct {
// UTXOSet ...
type UTXOSet struct {
// This can be used to iterate over. However, it should not be modified externally.
utxoMap map[[32]byte]int
Utxos []*spdagvm.UTXO
UTXOs []*spdagvm.UTXO
}
// Put ...
func (us *UtxoSet) Put(utxo *spdagvm.UTXO) {
func (us *UTXOSet) Put(utxo *spdagvm.UTXO) {
if us.utxoMap == nil {
us.utxoMap = make(map[[32]byte]int)
}
if _, ok := us.utxoMap[utxo.ID().Key()]; !ok {
us.utxoMap[utxo.ID().Key()] = len(us.Utxos)
us.Utxos = append(us.Utxos, utxo)
us.utxoMap[utxo.ID().Key()] = len(us.UTXOs)
us.UTXOs = append(us.UTXOs, utxo)
}
}
// Get ...
func (us *UtxoSet) Get(id ids.ID) *spdagvm.UTXO {
func (us *UTXOSet) Get(id ids.ID) *spdagvm.UTXO {
if us.utxoMap == nil {
return nil
}
if i, ok := us.utxoMap[id.Key()]; ok {
utxo := us.Utxos[i]
utxo := us.UTXOs[i]
return utxo
}
return nil
}
// Remove ...
func (us *UtxoSet) Remove(id ids.ID) *spdagvm.UTXO {
func (us *UTXOSet) Remove(id ids.ID) *spdagvm.UTXO {
i, ok := us.utxoMap[id.Key()]
if !ok {
return nil
}
utxoI := us.Utxos[i]
utxoI := us.UTXOs[i]
j := len(us.Utxos) - 1
utxoJ := us.Utxos[j]
j := len(us.UTXOs) - 1
utxoJ := us.UTXOs[j]
us.Utxos[i] = us.Utxos[j]
us.Utxos = us.Utxos[:j]
us.UTXOs[i] = us.UTXOs[j]
us.UTXOs = us.UTXOs[:j]
us.utxoMap[utxoJ.ID().Key()] = i
delete(us.utxoMap, utxoI.ID().Key())
@ -61,14 +61,14 @@ func (us *UtxoSet) Remove(id ids.ID) *spdagvm.UTXO {
return utxoI
}
func (us *UtxoSet) string(prefix string) string {
func (us *UTXOSet) string(prefix string) string {
s := strings.Builder{}
for i, utxo := range us.Utxos {
for i, utxo := range us.UTXOs {
out := utxo.Out().(*spdagvm.OutputPayment)
sourceID, sourceIndex := utxo.Source()
s.WriteString(fmt.Sprintf("%sUtxo[%d]:"+
s.WriteString(fmt.Sprintf("%sUTXO[%d]:"+
"\n%s InputID: %s"+
"\n%s InputIndex: %d"+
"\n%s Locktime: %d"+
@ -83,6 +83,6 @@ func (us *UtxoSet) string(prefix string) string {
return strings.TrimSuffix(s.String(), "\n")
}
func (us *UtxoSet) String() string {
func (us *UTXOSet) String() string {
return us.string("")
}

View File

@ -18,8 +18,8 @@ type Wallet struct {
networkID uint32
chainID ids.ID
clock timer.Clock
keyChain *spdagvm.KeyChain // Mapping from public address to the SigningKeys
utxoSet *UtxoSet // Mapping from utxoIDs to Utxos
keychain *spdagvm.Keychain // Mapping from public address to the SigningKeys
utxoSet *UTXOSet // Mapping from utxoIDs to UTXOs
balance uint64
txFee uint64
}
@ -29,8 +29,8 @@ func NewWallet(networkID uint32, chainID ids.ID, txFee uint64) Wallet {
return Wallet{
networkID: networkID,
chainID: chainID,
keyChain: &spdagvm.KeyChain{},
utxoSet: &UtxoSet{},
keychain: &spdagvm.Keychain{},
utxoSet: &UTXOSet{},
txFee: txFee,
}
}
@ -38,32 +38,32 @@ func NewWallet(networkID uint32, chainID ids.ID, txFee uint64) Wallet {
// GetAddress returns one of the addresses this wallet manages. If no address
// exists, one will be created.
func (w *Wallet) GetAddress() ids.ShortID {
if w.keyChain.Addrs.Len() == 0 {
if w.keychain.Addrs.Len() == 0 {
return w.CreateAddress()
}
return w.keyChain.Addrs.CappedList(1)[0]
return w.keychain.Addrs.CappedList(1)[0]
}
// CreateAddress returns a new address.
// It also saves the address and the private key that controls it
// so the address can be used later
func (w *Wallet) CreateAddress() ids.ShortID {
privKey, _ := w.keyChain.New()
privKey, _ := w.keychain.New()
return privKey.PublicKey().Address()
}
// ImportKey imports a private key into this wallet
func (w *Wallet) ImportKey(sk *crypto.PrivateKeySECP256K1R) { w.keyChain.Add(sk) }
func (w *Wallet) ImportKey(sk *crypto.PrivateKeySECP256K1R) { w.keychain.Add(sk) }
// AddUtxo adds a new UTXO to this wallet if this wallet may spend it
// AddUTXO adds a new UTXO to this wallet if this wallet may spend it
// The UTXO's output must be an OutputPayment
func (w *Wallet) AddUtxo(utxo *spdagvm.UTXO) {
func (w *Wallet) AddUTXO(utxo *spdagvm.UTXO) {
out, ok := utxo.Out().(*spdagvm.OutputPayment)
if !ok {
return
}
if _, _, err := w.keyChain.Spend(utxo, math.MaxUint64); err == nil {
if _, _, err := w.keychain.Spend(utxo, math.MaxUint64); err == nil {
w.utxoSet.Put(utxo)
w.balance += out.Amount()
}
@ -83,18 +83,18 @@ func (w *Wallet) Send(amount uint64, locktime uint64, destAddr ids.ShortID) *spd
// Send any change to an address this wallet controls
changeAddr := ids.ShortID{}
if w.keyChain.Addrs.Len() < 1000 {
if w.keychain.Addrs.Len() < 1000 {
changeAddr = w.CreateAddress()
} else {
changeAddr = w.GetAddress()
}
utxoList := w.utxoSet.Utxos // List of UTXOs this wallet may spend
utxoList := w.utxoSet.UTXOs // List of UTXOs this wallet may spend
destAddrs := []ids.ShortID{destAddr}
// Build the transaction
tx, err := builder.NewTxFromUTXOs(w.keyChain, utxoList, amount, w.txFee, locktime, 1, destAddrs, changeAddr, currentTime)
tx, err := builder.NewTxFromUTXOs(w.keychain, utxoList, amount, w.txFee, locktime, 1, destAddrs, changeAddr, currentTime)
if err != nil {
panic(err)
}
@ -102,8 +102,8 @@ func (w *Wallet) Send(amount uint64, locktime uint64, destAddr ids.ShortID) *spd
// Remove from [w.utxoSet] any UTXOs used to fund [tx]
for _, in := range tx.Ins() {
if in, ok := in.(*spdagvm.InputPayment); ok {
inUtxoID := in.InputID()
w.utxoSet.Remove(inUtxoID)
inUTXOID := in.InputID()
w.utxoSet.Remove(inUTXOID)
w.balance -= in.Amount() // Deduct from [w.balance] the amount sent
}
}
@ -113,10 +113,10 @@ func (w *Wallet) Send(amount uint64, locktime uint64, destAddr ids.ShortID) *spd
func (w Wallet) String() string {
return fmt.Sprintf(
"KeyChain:\n"+
"Keychain:\n"+
"%s\n"+
"UtxoSet:\n"+
"UTXOSet:\n"+
"%s",
w.keyChain.PrefixedString(" "),
w.keychain.PrefixedString(" "),
w.utxoSet.string(" "))
}

View File

@ -3,75 +3,22 @@
package main
// #include "salticidae/network.h"
// void onTerm(int sig, void *);
// void decidedTx(msg_t *, msgnetwork_conn_t *, void *);
import "C"
import (
"fmt"
"os"
"path"
"runtime"
"runtime/pprof"
"time"
"unsafe"
"github.com/ava-labs/salticidae-go"
"github.com/ava-labs/gecko/genesis"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/networking"
"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/utils/timer"
"github.com/ava-labs/gecko/vms/platformvm"
"github.com/ava-labs/gecko/vms/spchainvm"
"github.com/ava-labs/gecko/vms/spdagvm"
"github.com/ava-labs/gecko/xputtest/chainwallet"
"github.com/ava-labs/gecko/xputtest/dagwallet"
)
// tp stores the persistent data needed when running the test.
type tp struct {
ec salticidae.EventContext
build networking.Builder
conn salticidae.MsgNetworkConn
log logging.Logger
decided chan ids.ID
networkID uint32
}
var t = tp{}
//export onTerm
func onTerm(C.int, unsafe.Pointer) {
t.log.Info("Terminate signal received")
t.ec.Stop()
}
// decidedTx handles the recept of a decidedTx message
//export decidedTx
func decidedTx(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
msg := salticidae.MsgFromC(salticidae.CMsg(_msg))
pMsg, err := t.build.Parse(networking.DecidedTx, msg.GetPayloadByMove())
if err != nil {
t.log.Warn("Failed to parse DecidedTx message")
return
}
txID, err := ids.ToID(pMsg.Get(networking.TxID).([]byte))
t.log.AssertNoError(err) // Length is checked in message parsing
t.log.Debug("Decided %s", txID)
t.decided <- txID
}
func main() {
if err != nil {
fmt.Printf("Failed to parse arguments: %s\n", err)
@ -86,41 +33,28 @@ func main() {
defer log.Stop()
t.log = log
net.log = log
crypto.EnableCrypto = config.EnableCrypto
t.decided = make(chan ids.ID, config.MaxOutstandingTxs)
net.decided = make(chan ids.ID, config.MaxOutstandingTxs)
if config.Key >= len(genesis.Keys) || config.Key < 0 {
log.Fatal("Unknown key specified")
return
}
t.ec = salticidae.NewEventContext()
evInt := salticidae.NewSigEvent(t.ec, salticidae.SigEventCallback(C.onTerm), nil)
evInt.Add(salticidae.SIGINT)
evTerm := salticidae.NewSigEvent(t.ec, salticidae.SigEventCallback(C.onTerm), nil)
evTerm.Add(salticidae.SIGTERM)
log.AssertNoError(net.Initialize())
net.net.Start()
defer net.net.Stop()
serr := salticidae.NewError()
netconfig := salticidae.NewMsgNetworkConfig()
net := salticidae.NewMsgNetwork(t.ec, netconfig, &serr)
if serr.GetCode() != 0 {
log.Fatal("Sync error %s", salticidae.StrError(serr.GetCode()))
return
}
net.RegHandler(networking.DecidedTx, salticidae.MsgNetworkMsgCallback(C.decidedTx), nil)
net.Start()
defer net.Stop()
remoteIP := salticidae.NewNetAddrFromIPPortString(config.RemoteIP.String(), true, &serr)
if code := serr.GetCode(); code != 0 {
log.Fatal("Sync error %s", salticidae.StrError(serr.GetCode()))
return
}
t.conn = net.ConnectSync(remoteIP, true, &serr)
net.conn = net.net.ConnectSync(remoteIP, true, &serr)
if serr.GetCode() != 0 {
log.Fatal("Sync error %s", salticidae.StrError(serr.GetCode()))
return
@ -135,192 +69,24 @@ func main() {
defer file.Close()
defer pprof.StopCPUProfile()
t.networkID = config.NetworkID
net.networkID = config.NetworkID
platformGenesisBytes := genesis.Genesis(net.networkID)
genesisState := &platformvm.Genesis{}
log.AssertNoError(platformvm.Codec.Unmarshal(platformGenesisBytes, genesisState))
log.AssertNoError(genesisState.Initialize())
switch config.Chain {
case ChainChain:
t.benchmarkSnowman()
case DagChain:
t.benchmarkAvalanche()
case spChain:
net.benchmarkSPChain(genesisState)
case spDAG:
net.benchmarkSPDAG(genesisState)
case avmDAG:
net.benchmarkAVM(genesisState)
default:
t.log.Fatal("did not specify whether to test dag or chain. Exiting")
log.Fatal("did not specify whether to test dag or chain. Exiting")
return
}
t.ec.Dispatch()
}
func (t *tp) benchmarkAvalanche() {
platformGenesisBytes := genesis.Genesis(t.networkID)
genesisState := &platformvm.Genesis{}
err := platformvm.Codec.Unmarshal(platformGenesisBytes, genesisState)
t.log.AssertNoError(err)
t.log.AssertNoError(genesisState.Initialize())
spDAGChain := genesisState.Chains[2]
if name := spDAGChain.ChainName; name != "Simple DAG Payments" {
panic("Wrong chain name")
}
genesisBytes := spDAGChain.GenesisData
wallet := dagwallet.NewWallet(t.networkID, spDAGChain.ID(), config.AvaTxFee)
codec := spdagvm.Codec{}
tx, err := codec.UnmarshalTx(genesisBytes)
t.log.AssertNoError(err)
cb58 := formatting.CB58{}
keyStr := genesis.Keys[config.Key]
t.log.AssertNoError(cb58.FromString(keyStr))
factory := crypto.FactorySECP256K1R{}
skGen, err := factory.ToPrivateKey(cb58.Bytes)
t.log.AssertNoError(err)
sk := skGen.(*crypto.PrivateKeySECP256K1R)
wallet.ImportKey(sk)
for _, utxo := range tx.UTXOs() {
wallet.AddUtxo(utxo)
}
go t.log.RecoverAndPanic(func() { t.IssueAvalanche(spDAGChain.ID(), wallet) })
}
func (t *tp) IssueAvalanche(chainID ids.ID, wallet dagwallet.Wallet) {
t.log.Info("starting avalanche benchmark")
pending := make(map[[32]byte]*spdagvm.Tx)
canAdd := []*spdagvm.Tx{}
numAccepted := 0
t.decided <- ids.ID{}
meter := timer.TimedMeter{Duration: time.Second}
for d := range t.decided {
if numAccepted%1000 == 0 {
t.log.Info("TPS: %d", meter.Ticks())
}
if !d.IsZero() {
meter.Tick()
key := d.Key()
if tx := pending[key]; tx != nil {
canAdd = append(canAdd, tx)
t.log.Debug("Finalized %s", d)
delete(pending, key)
numAccepted++
}
}
for len(pending) < config.MaxOutstandingTxs && (wallet.Balance() > 0 || len(canAdd) > 0) {
if wallet.Balance() == 0 {
tx := canAdd[0]
canAdd = canAdd[1:]
for _, utxo := range tx.UTXOs() {
wallet.AddUtxo(utxo)
}
}
tx := wallet.Send(1, 0, wallet.GetAddress())
t.log.AssertTrue(tx != nil, "Tx creation failed")
it, err := t.build.IssueTx(chainID, tx.Bytes())
t.log.AssertNoError(err)
ds := it.DataStream()
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
newMsg := salticidae.NewMsgMovedFromByteArray(networking.IssueTx, ba, false)
t.conn.GetNet().SendMsg(newMsg, t.conn)
ds.Free()
ba.Free()
newMsg.Free()
pending[tx.ID().Key()] = tx
t.log.Debug("Sent tx, pending = %d, accepted = %d", len(pending), numAccepted)
}
}
}
func (t *tp) benchmarkSnowman() {
platformGenesisBytes := genesis.Genesis(t.networkID)
genesisState := &platformvm.Genesis{}
err := platformvm.Codec.Unmarshal(platformGenesisBytes, genesisState)
t.log.AssertNoError(err)
t.log.AssertNoError(genesisState.Initialize())
spchainChain := genesisState.Chains[3]
if name := spchainChain.ChainName; name != "Simple Chain Payments" {
panic("Wrong chain name")
}
genesisBytes := spchainChain.GenesisData
wallet := chainwallet.NewWallet(t.networkID, spchainChain.ID())
codec := spchainvm.Codec{}
accounts, err := codec.UnmarshalGenesis(genesisBytes)
t.log.AssertNoError(err)
cb58 := formatting.CB58{}
factory := crypto.FactorySECP256K1R{}
for _, keyStr := range genesis.Keys {
t.log.AssertNoError(cb58.FromString(keyStr))
skGen, err := factory.ToPrivateKey(cb58.Bytes)
t.log.AssertNoError(err)
sk := skGen.(*crypto.PrivateKeySECP256K1R)
wallet.ImportKey(sk)
}
for _, account := range accounts {
wallet.AddAccount(account)
break
}
wallet.GenerateTxs()
go t.log.RecoverAndPanic(func() { t.IssueSnowman(spchainChain.ID(), wallet) })
}
func (t *tp) IssueSnowman(chainID ids.ID, wallet chainwallet.Wallet) {
t.log.Debug("Issuing with %d", wallet.Balance())
numAccepted := 0
numPending := 0
t.decided <- ids.ID{}
meter := timer.TimedMeter{Duration: time.Second}
for d := range t.decided {
if numAccepted%1000 == 0 {
t.log.Info("TPS: %d", meter.Ticks())
}
if !d.IsZero() {
meter.Tick()
t.log.Debug("Finalized %s", d)
numAccepted++
numPending--
}
for numPending < config.MaxOutstandingTxs && wallet.Balance() > 0 && wallet.TxsSent < chainwallet.MaxNumTxs {
tx := wallet.NextTx()
t.log.AssertTrue(tx != nil, "Tx creation failed")
it, err := t.build.IssueTx(chainID, tx.Bytes())
t.log.AssertNoError(err)
ds := it.DataStream()
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
newMsg := salticidae.NewMsgMovedFromByteArray(networking.IssueTx, ba, false)
t.conn.GetNet().SendMsg(newMsg, t.conn)
ds.Free()
ba.Free()
newMsg.Free()
numPending++
t.log.Debug("Sent tx, pending = %d, accepted = %d", numPending, numAccepted)
}
if wallet.TxsSent >= chainwallet.MaxNumTxs {
fmt.Println("done with test")
return
}
}
net.ec.Dispatch()
}

78
xputtest/network.go Normal file
View File

@ -0,0 +1,78 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main
// #include "salticidae/network.h"
// void onTerm(int sig, void *);
// void decidedTx(msg_t *, msgnetwork_conn_t *, void *);
import "C"
import (
"fmt"
"unsafe"
"github.com/ava-labs/salticidae-go"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/networking"
"github.com/ava-labs/gecko/utils/logging"
)
// network stores the persistent data needed when running the test.
type network struct {
ec salticidae.EventContext
build networking.Builder
net salticidae.MsgNetwork
conn salticidae.MsgNetworkConn
log logging.Logger
decided chan ids.ID
networkID uint32
}
var net = network{}
func (n *network) Initialize() error {
n.ec = salticidae.NewEventContext()
evInt := salticidae.NewSigEvent(n.ec, salticidae.SigEventCallback(C.onTerm), nil)
evInt.Add(salticidae.SIGINT)
evTerm := salticidae.NewSigEvent(n.ec, salticidae.SigEventCallback(C.onTerm), nil)
evTerm.Add(salticidae.SIGTERM)
serr := salticidae.NewError()
netconfig := salticidae.NewMsgNetworkConfig()
n.net = salticidae.NewMsgNetwork(n.ec, netconfig, &serr)
if serr.GetCode() != 0 {
return fmt.Errorf("sync error %s", salticidae.StrError(serr.GetCode()))
}
n.net.RegHandler(networking.DecidedTx, salticidae.MsgNetworkMsgCallback(C.decidedTx), nil)
return nil
}
//export onTerm
func onTerm(C.int, unsafe.Pointer) {
net.log.Info("Terminate signal received")
net.ec.Stop()
}
// decidedTx handles the recept of a decidedTx message
//export decidedTx
func decidedTx(_msg *C.struct_msg_t, _conn *C.struct_msgnetwork_conn_t, _ unsafe.Pointer) {
msg := salticidae.MsgFromC(salticidae.CMsg(_msg))
pMsg, err := net.build.Parse(networking.DecidedTx, msg.GetPayloadByMove())
if err != nil {
net.log.Warn("Failed to parse DecidedTx message")
return
}
txID, err := ids.ToID(pMsg.Get(networking.TxID).([]byte))
net.log.AssertNoError(err) // Length is checked in message parsing
net.log.Debug("Decided %s", txID)
net.decided <- txID
}

View File

@ -6,7 +6,8 @@ package main
import (
"flag"
"fmt"
"net"
stdnet "net"
"github.com/ava-labs/gecko/genesis"
"github.com/ava-labs/gecko/utils"
@ -48,10 +49,12 @@ func init() {
logLevel := flag.String("log-level", "info", "The log level. Should be one of {all, debug, info, warn, error, fatal, off}")
// Test Variables:
chain := flag.Bool("chain", false, "Execute chain transactions")
dag := flag.Bool("dag", false, "Execute dag transactions")
spchain := flag.Bool("sp-chain", false, "Execute simple payment chain transactions")
spdag := flag.Bool("sp-dag", false, "Execute simple payment dag transactions")
avm := flag.Bool("avm", false, "Execute avm transactions")
flag.IntVar(&config.Key, "key", 0, "Index of the genesis key list to use")
flag.IntVar(&config.MaxOutstandingTxs, "max_outstanding", 1000, "Maximum number of transactions to leave outstanding")
flag.IntVar(&config.NumTxs, "num-txs", 25000, "Total number of transaction to issue")
flag.IntVar(&config.MaxOutstandingTxs, "max-outstanding", 1000, "Maximum number of transactions to leave outstanding")
flag.Parse()
@ -65,7 +68,7 @@ func init() {
config.NetworkID = networkID
// Remote:
parsedIP := net.ParseIP(*ip)
parsedIP := stdnet.ParseIP(*ip)
if parsedIP == nil {
errs.Add(fmt.Errorf("invalid IP Address %s", *ip))
}
@ -86,11 +89,13 @@ func init() {
// Test Variables:
switch {
case *chain:
config.Chain = ChainChain
case *dag:
config.Chain = DagChain
case *spchain:
config.Chain = spChain
case *spdag:
config.Chain = spDAG
case *avm:
config.Chain = avmDAG
default:
config.Chain = UnknownChain
config.Chain = unknown
}
}

96
xputtest/spchain.go Normal file
View File

@ -0,0 +1,96 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main
import (
"time"
"github.com/ava-labs/salticidae-go"
"github.com/ava-labs/gecko/genesis"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/networking"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/timer"
"github.com/ava-labs/gecko/vms/platformvm"
"github.com/ava-labs/gecko/vms/spchainvm"
"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())
codec := spchainvm.Codec{}
accounts, err := codec.UnmarshalGenesis(genesisBytes)
n.log.AssertNoError(err)
cb58 := formatting.CB58{}
factory := crypto.FactorySECP256K1R{}
for _, keyStr := range genesis.Keys {
n.log.AssertNoError(cb58.FromString(keyStr))
skGen, err := factory.ToPrivateKey(cb58.Bytes)
n.log.AssertNoError(err)
sk := skGen.(*crypto.PrivateKeySECP256K1R)
wallet.ImportKey(sk)
}
for _, account := range accounts {
wallet.AddAccount(account)
break
}
n.log.AssertNoError(wallet.GenerateTxs(config.NumTxs))
go n.log.RecoverAndPanic(func() { n.IssueSPChain(spchainChain.ID(), wallet) })
}
func (n *network) IssueSPChain(chainID ids.ID, wallet chainwallet.Wallet) {
n.log.Debug("Issuing with %d", wallet.Balance())
numAccepted := 0
numPending := 0
n.decided <- ids.ID{}
meter := timer.TimedMeter{Duration: time.Second}
for d := range n.decided {
if numAccepted%1000 == 0 {
n.log.Info("TPS: %d", meter.Ticks())
}
if !d.IsZero() {
meter.Tick()
n.log.Debug("Finalized %s", d)
numAccepted++
numPending--
}
for numPending < config.MaxOutstandingTxs && wallet.Balance() > 0 && numAccepted+numPending < config.NumTxs {
tx := wallet.NextTx()
n.log.AssertTrue(tx != nil, "Tx creation failed")
it, err := n.build.IssueTx(chainID, tx.Bytes())
n.log.AssertNoError(err)
ds := it.DataStream()
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
newMsg := salticidae.NewMsgMovedFromByteArray(networking.IssueTx, ba, false)
n.conn.GetNet().SendMsg(newMsg, n.conn)
ds.Free()
ba.Free()
newMsg.Free()
numPending++
n.log.Debug("Sent tx, pending = %d, accepted = %d", numPending, numAccepted)
}
if numAccepted+numPending >= config.NumTxs {
n.log.Info("done with test")
return
}
}
}

102
xputtest/spdag.go Normal file
View File

@ -0,0 +1,102 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main
import (
"time"
"github.com/ava-labs/salticidae-go"
"github.com/ava-labs/gecko/genesis"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/networking"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/timer"
"github.com/ava-labs/gecko/vms/platformvm"
"github.com/ava-labs/gecko/vms/spdagvm"
"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)
codec := spdagvm.Codec{}
tx, err := codec.UnmarshalTx(genesisBytes)
n.log.AssertNoError(err)
cb58 := formatting.CB58{}
keyStr := genesis.Keys[config.Key]
n.log.AssertNoError(cb58.FromString(keyStr))
factory := crypto.FactorySECP256K1R{}
skGen, err := factory.ToPrivateKey(cb58.Bytes)
n.log.AssertNoError(err)
sk := skGen.(*crypto.PrivateKeySECP256K1R)
wallet.ImportKey(sk)
for _, utxo := range tx.UTXOs() {
wallet.AddUTXO(utxo)
}
go n.log.RecoverAndPanic(func() { n.IssueSPDAG(spDAGChain.ID(), 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{}
numAccepted := 0
n.decided <- ids.ID{}
meter := timer.TimedMeter{Duration: time.Second}
for d := range n.decided {
if numAccepted%1000 == 0 {
n.log.Info("TPS: %d", meter.Ticks())
}
if !d.IsZero() {
meter.Tick()
key := d.Key()
if tx := pending[key]; tx != nil {
canAdd = append(canAdd, tx)
n.log.Debug("Finalized %s", d)
delete(pending, key)
numAccepted++
}
}
for len(pending) < config.MaxOutstandingTxs && (wallet.Balance() > 0 || len(canAdd) > 0) {
if wallet.Balance() == 0 {
tx := canAdd[0]
canAdd = canAdd[1:]
for _, utxo := range tx.UTXOs() {
wallet.AddUTXO(utxo)
}
}
tx := wallet.Send(1, 0, wallet.GetAddress())
n.log.AssertTrue(tx != nil, "Tx creation failed")
it, err := n.build.IssueTx(chainID, tx.Bytes())
n.log.AssertNoError(err)
ds := it.DataStream()
ba := salticidae.NewByteArrayMovedFromDataStream(ds, false)
newMsg := salticidae.NewMsgMovedFromByteArray(networking.IssueTx, ba, false)
n.conn.GetNet().SendMsg(newMsg, n.conn)
ds.Free()
ba.Free()
newMsg.Free()
pending[tx.ID().Key()] = tx
n.log.Debug("Sent tx, pending = %d, accepted = %d", len(pending), numAccepted)
}
}
}