Merge branch 'master' into atomic

This commit is contained in:
StephenButtolph 2020-03-18 17:40:16 -04:00
commit 3813086d6f
24 changed files with 471 additions and 178 deletions

View File

@ -21,10 +21,30 @@ import (
"github.com/ava-labs/gecko/vms/components/codec" "github.com/ava-labs/gecko/vms/components/codec"
jsoncodec "github.com/ava-labs/gecko/utils/json" jsoncodec "github.com/ava-labs/gecko/utils/json"
zxcvbn "github.com/nbutton23/zxcvbn-go"
)
const (
// maxUserPassLen is the maximum length of the username or password allowed
maxUserPassLen = 1024
// requiredPassScore defines the score a password must achieve to be accepted
// as a password with strong characteristics by the zxcvbn package
//
// The scoring mechanism defined is as follows;
//
// 0 # too guessable: risky password. (guesses < 10^3)
// 1 # very guessable: protection from throttled online attacks. (guesses < 10^6)
// 2 # somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)
// 3 # safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)
// 4 # very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)
requiredPassScore = 2
) )
var ( var (
errEmptyUsername = errors.New("username can't be the empty string") errEmptyUsername = errors.New("username can't be the empty string")
errUserPassMaxLength = fmt.Errorf("CreateUser call rejected due to username or password exceeding maximum length of %d chars", maxUserPassLen)
errWeakPassword = errors.New("Failed to create user as the given password is too weak. A stronger password is one of 8 or more characters containing attributes of upper and lowercase letters, numbers, and/or special characters")
) )
// KeyValuePair ... // KeyValuePair ...
@ -114,7 +134,11 @@ func (ks *Keystore) CreateUser(_ *http.Request, args *CreateUserArgs, reply *Cre
ks.lock.Lock() ks.lock.Lock()
defer ks.lock.Unlock() defer ks.lock.Unlock()
ks.log.Verbo("CreateUser called with %s", args.Username) ks.log.Verbo("CreateUser called with %.*s", maxUserPassLen, args.Username)
if len(args.Username) > maxUserPassLen || len(args.Password) > maxUserPassLen {
return errUserPassMaxLength
}
if args.Username == "" { if args.Username == "" {
return errEmptyUsername return errEmptyUsername
@ -123,6 +147,10 @@ func (ks *Keystore) CreateUser(_ *http.Request, args *CreateUserArgs, reply *Cre
return fmt.Errorf("user already exists: %s", args.Username) return fmt.Errorf("user already exists: %s", args.Username)
} }
if zxcvbn.PasswordStrength(args.Password, nil).Score < requiredPassScore {
return errWeakPassword
}
usr := &User{} usr := &User{}
if err := usr.Initialize(args.Password); err != nil { if err := usr.Initialize(args.Password); err != nil {
return err return err

View File

@ -5,6 +5,8 @@ package keystore
import ( import (
"bytes" "bytes"
"fmt"
"math/rand"
"testing" "testing"
"github.com/ava-labs/gecko/database/memdb" "github.com/ava-labs/gecko/database/memdb"
@ -12,6 +14,12 @@ import (
"github.com/ava-labs/gecko/utils/logging" "github.com/ava-labs/gecko/utils/logging"
) )
var (
// strongPassword defines a password used for the following tests that
// scores high enough to pass the password strength scoring system
strongPassword = "N_+=_jJ;^(<;{4,:*m6CET}'&N;83FYK.wtNpwp-Jt"
)
func TestServiceListNoUsers(t *testing.T) { func TestServiceListNoUsers(t *testing.T) {
ks := Keystore{} ks := Keystore{}
ks.Initialize(logging.NoLog{}, memdb.New()) ks.Initialize(logging.NoLog{}, memdb.New())
@ -33,7 +41,7 @@ func TestServiceCreateUser(t *testing.T) {
reply := CreateUserReply{} reply := CreateUserReply{}
if err := ks.CreateUser(nil, &CreateUserArgs{ if err := ks.CreateUser(nil, &CreateUserArgs{
Username: "bob", Username: "bob",
Password: "launch", Password: strongPassword,
}, &reply); err != nil { }, &reply); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -56,6 +64,78 @@ func TestServiceCreateUser(t *testing.T) {
} }
} }
// genStr returns a string of given length
func genStr(n int) string {
b := make([]byte, n)
rand.Read(b)
return fmt.Sprintf("%x", b)[:n]
}
// TestServiceCreateUserArgsChecks generates excessively long usernames or
// passwords to assure the santity checks on string length are not exceeded
func TestServiceCreateUserArgsCheck(t *testing.T) {
ks := Keystore{}
ks.Initialize(logging.NoLog{}, memdb.New())
{
reply := CreateUserReply{}
err := ks.CreateUser(nil, &CreateUserArgs{
Username: genStr(maxUserPassLen + 1),
Password: strongPassword,
}, &reply)
if reply.Success || err != errUserPassMaxLength {
t.Fatal("User was created when it should have been rejected due to too long a Username, err =", err)
}
}
{
reply := CreateUserReply{}
err := ks.CreateUser(nil, &CreateUserArgs{
Username: "shortuser",
Password: genStr(maxUserPassLen + 1),
}, &reply)
if reply.Success || err != errUserPassMaxLength {
t.Fatal("User was created when it should have been rejected due to too long a Password, err =", err)
}
}
{
reply := ListUsersReply{}
if err := ks.ListUsers(nil, &ListUsersArgs{}, &reply); err != nil {
t.Fatal(err)
}
if len(reply.Users) > 0 {
t.Fatalf("A user exists when there should be none")
}
}
}
// TestServiceCreateUserWeakPassword tests creating a new user with a weak
// password to ensure the password strength check is working
func TestServiceCreateUserWeakPassword(t *testing.T) {
ks := Keystore{}
ks.Initialize(logging.NoLog{}, memdb.New())
{
reply := CreateUserReply{}
err := ks.CreateUser(nil, &CreateUserArgs{
Username: "bob",
Password: "weak",
}, &reply)
if err != errWeakPassword {
t.Error("Unexpected error occurred when testing weak password:", err)
}
if reply.Success {
t.Fatal("User was created when it should have been rejected due to weak password")
}
}
}
func TestServiceCreateDuplicate(t *testing.T) { func TestServiceCreateDuplicate(t *testing.T) {
ks := Keystore{} ks := Keystore{}
ks.Initialize(logging.NoLog{}, memdb.New()) ks.Initialize(logging.NoLog{}, memdb.New())
@ -64,7 +144,7 @@ func TestServiceCreateDuplicate(t *testing.T) {
reply := CreateUserReply{} reply := CreateUserReply{}
if err := ks.CreateUser(nil, &CreateUserArgs{ if err := ks.CreateUser(nil, &CreateUserArgs{
Username: "bob", Username: "bob",
Password: "launch", Password: strongPassword,
}, &reply); err != nil { }, &reply); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -77,7 +157,7 @@ func TestServiceCreateDuplicate(t *testing.T) {
reply := CreateUserReply{} reply := CreateUserReply{}
if err := ks.CreateUser(nil, &CreateUserArgs{ if err := ks.CreateUser(nil, &CreateUserArgs{
Username: "bob", Username: "bob",
Password: "launch!", Password: strongPassword,
}, &reply); err == nil { }, &reply); err == nil {
t.Fatalf("Should have errored due to the username already existing") t.Fatalf("Should have errored due to the username already existing")
} }
@ -90,7 +170,7 @@ func TestServiceCreateUserNoName(t *testing.T) {
reply := CreateUserReply{} reply := CreateUserReply{}
if err := ks.CreateUser(nil, &CreateUserArgs{ if err := ks.CreateUser(nil, &CreateUserArgs{
Password: "launch", Password: strongPassword,
}, &reply); err == nil { }, &reply); err == nil {
t.Fatalf("Shouldn't have allowed empty username") t.Fatalf("Shouldn't have allowed empty username")
} }
@ -104,7 +184,7 @@ func TestServiceUseBlockchainDB(t *testing.T) {
reply := CreateUserReply{} reply := CreateUserReply{}
if err := ks.CreateUser(nil, &CreateUserArgs{ if err := ks.CreateUser(nil, &CreateUserArgs{
Username: "bob", Username: "bob",
Password: "launch", Password: strongPassword,
}, &reply); err != nil { }, &reply); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -114,7 +194,7 @@ func TestServiceUseBlockchainDB(t *testing.T) {
} }
{ {
db, err := ks.GetDatabase(ids.Empty, "bob", "launch") db, err := ks.GetDatabase(ids.Empty, "bob", strongPassword)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -124,7 +204,7 @@ func TestServiceUseBlockchainDB(t *testing.T) {
} }
{ {
db, err := ks.GetDatabase(ids.Empty, "bob", "launch") db, err := ks.GetDatabase(ids.Empty, "bob", strongPassword)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -144,7 +224,7 @@ func TestServiceExportImport(t *testing.T) {
reply := CreateUserReply{} reply := CreateUserReply{}
if err := ks.CreateUser(nil, &CreateUserArgs{ if err := ks.CreateUser(nil, &CreateUserArgs{
Username: "bob", Username: "bob",
Password: "launch", Password: strongPassword,
}, &reply); err != nil { }, &reply); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -154,7 +234,7 @@ func TestServiceExportImport(t *testing.T) {
} }
{ {
db, err := ks.GetDatabase(ids.Empty, "bob", "launch") db, err := ks.GetDatabase(ids.Empty, "bob", strongPassword)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -166,7 +246,7 @@ func TestServiceExportImport(t *testing.T) {
exportReply := ExportUserReply{} exportReply := ExportUserReply{}
if err := ks.ExportUser(nil, &ExportUserArgs{ if err := ks.ExportUser(nil, &ExportUserArgs{
Username: "bob", Username: "bob",
Password: "launch", Password: strongPassword,
}, &exportReply); err != nil { }, &exportReply); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -178,7 +258,7 @@ func TestServiceExportImport(t *testing.T) {
reply := ImportUserReply{} reply := ImportUserReply{}
if err := newKS.ImportUser(nil, &ImportUserArgs{ if err := newKS.ImportUser(nil, &ImportUserArgs{
Username: "bob", Username: "bob",
Password: "launch", Password: strongPassword,
User: exportReply.User, User: exportReply.User,
}, &reply); err != nil { }, &reply); err != nil {
t.Fatal(err) t.Fatal(err)
@ -189,7 +269,7 @@ func TestServiceExportImport(t *testing.T) {
} }
{ {
db, err := newKS.GetDatabase(ids.Empty, "bob", "launch") db, err := newKS.GetDatabase(ids.Empty, "bob", strongPassword)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -504,6 +504,9 @@ func VMGenesis(networkID uint32, vmID ids.ID) *platformvm.CreateChainTx {
genesisBytes := Genesis(networkID) genesisBytes := Genesis(networkID)
genesis := platformvm.Genesis{} genesis := platformvm.Genesis{}
platformvm.Codec.Unmarshal(genesisBytes, &genesis) platformvm.Codec.Unmarshal(genesisBytes, &genesis)
if err := genesis.Initialize(); err != nil {
panic(err)
}
for _, chain := range genesis.Chains { for _, chain := range genesis.Chains {
if chain.VMID.Equals(vmID) { if chain.VMID.Equals(vmID) {
return chain return chain

View File

@ -95,7 +95,7 @@ func (b *Bag) Mode() (ID, int) { return b.mode, b.modeFreq }
func (b *Bag) Threshold() Set { return b.metThreshold } func (b *Bag) Threshold() Set { return b.metThreshold }
// Filter returns the bag of ids with the same counts as this bag, except all // Filter returns the bag of ids with the same counts as this bag, except all
// the ids in the returned bag must have the same bits in the range [start, end] // the ids in the returned bag must have the same bits in the range [start, end)
// as id. // as id.
func (b *Bag) Filter(start, end int, id ID) Bag { func (b *Bag) Filter(start, end int, id ID) Bag {
newBag := Bag{} newBag := Bag{}

View File

@ -409,13 +409,18 @@ func (u *unaryNode) RecordPoll(votes ids.Bag, reset bool) node {
u.snowball.RecordSuccessfulPoll() u.snowball.RecordSuccessfulPoll()
if u.child != nil { if u.child != nil {
decidedPrefix := u.child.DecidedPrefix() // We are guaranteed that u.commonPrefix will equal
filteredVotes := votes.Filter(u.commonPrefix, decidedPrefix, u.preference) // u.child.DecidedPrefix(). Otherwise, there must have been a
// decision under this node, which isn't possible because
// beta1 <= beta2. That means that filtering the votes between
// u.commonPrefix and u.child.DecidedPrefix() would always result in
// the same set being returned.
// If I'm now decided, return my child // If I'm now decided, return my child
if u.Finalized() { if u.Finalized() {
return u.child.RecordPoll(filteredVotes, u.shouldReset) return u.child.RecordPoll(votes, u.shouldReset)
} }
u.child = u.child.RecordPoll(filteredVotes, u.shouldReset) u.child = u.child.RecordPoll(votes, u.shouldReset)
// The child's preference may have changed // The child's preference may have changed
u.preference = u.child.Preference() u.preference = u.child.Preference()
} }

View File

@ -256,7 +256,7 @@ func TestSnowballTrinary(t *testing.T) {
tree.RecordPoll(redBag) tree.RecordPoll(redBag)
if pref := tree.Preference(); !Blue.Equals(pref) { if pref := tree.Preference(); !Blue.Equals(pref) {
t.Fatalf("Wrong preference. Expected %s got %s", Green, pref) t.Fatalf("Wrong preference. Expected %s got %s", Blue, pref)
} else if tree.Finalized() { } else if tree.Finalized() {
t.Fatalf("Finalized too early") t.Fatalf("Finalized too early")
} }
@ -533,3 +533,108 @@ func TestSnowballConsistent(t *testing.T) {
t.Fatalf("Network agreed on inconsistent values") t.Fatalf("Network agreed on inconsistent values")
} }
} }
func TestSnowballFilterBinaryChildren(t *testing.T) {
c0000 := ids.NewID([32]byte{0b00000000})
c1000 := ids.NewID([32]byte{0b00000001})
c0100 := ids.NewID([32]byte{0b00000010})
c0010 := ids.NewID([32]byte{0b00000100})
params := Parameters{
Metrics: prometheus.NewRegistry(),
K: 1, Alpha: 1, BetaVirtuous: 1, BetaRogue: 2,
}
tree := Tree{}
tree.Initialize(params, c0000)
{
expected := "SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [0, 256)"
if pref := tree.Preference(); !c0000.Equals(pref) {
t.Fatalf("Wrong preference. Expected %s got %s", c0000, pref)
} else if tree.Finalized() {
t.Fatalf("Finalized too early")
} else if str := tree.String(); expected != str {
t.Fatalf("Wrong string. Expected:\n%s\ngot:\n%s", expected, str)
}
}
tree.Add(c1000)
{
expected := "SB(Preference = 0, NumSuccessfulPolls[0] = 0, NumSuccessfulPolls[1] = 0, SF = SF(Preference = 0, Confidence = 0, Finalized = false)) Bit = 0\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [1, 256)\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [1, 256)"
if pref := tree.Preference(); !c0000.Equals(pref) {
t.Fatalf("Wrong preference. Expected %s got %s", c0000, pref)
} else if tree.Finalized() {
t.Fatalf("Finalized too early")
} else if str := tree.String(); expected != str {
t.Fatalf("Wrong string. Expected:\n%s\ngot:\n%s", expected, str)
}
}
tree.Add(c0010)
{
expected := "SB(Preference = 0, NumSuccessfulPolls[0] = 0, NumSuccessfulPolls[1] = 0, SF = SF(Preference = 0, Confidence = 0, Finalized = false)) Bit = 0\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [1, 2)\n"+
" SB(Preference = 0, NumSuccessfulPolls[0] = 0, NumSuccessfulPolls[1] = 0, SF = SF(Preference = 0, Confidence = 0, Finalized = false)) Bit = 2\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [3, 256)\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [3, 256)\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [1, 256)"
if pref := tree.Preference(); !c0000.Equals(pref) {
t.Fatalf("Wrong preference. Expected %s got %s", c0000, pref)
} else if tree.Finalized() {
t.Fatalf("Finalized too early")
} else if str := tree.String(); expected != str {
t.Fatalf("Wrong string. Expected:\n%s\ngot:\n%s", expected, str)
}
}
c0000Bag := ids.Bag{}
c0000Bag.Add(c0000)
tree.RecordPoll(c0000Bag)
{
expected := "SB(Preference = 0, NumSuccessfulPolls[0] = 1, NumSuccessfulPolls[1] = 0, SF = SF(Preference = 0, Confidence = 1, Finalized = false)) Bit = 0\n"+
" SB(Preference = 0, NumSuccessfulPolls[0] = 1, NumSuccessfulPolls[1] = 0, SF = SF(Preference = 0, Confidence = 1, Finalized = false)) Bit = 2\n"+
" SB(NumSuccessfulPolls = 1, Confidence = 1, Finalized = true) Bits = [3, 256)\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [3, 256)\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [1, 256)"
if pref := tree.Preference(); !c0000.Equals(pref) {
t.Fatalf("Wrong preference. Expected %s got %s", c0000, pref)
} else if tree.Finalized() {
t.Fatalf("Finalized too early")
} else if str := tree.String(); expected != str {
t.Fatalf("Wrong string. Expected:\n%s\ngot:\n%s", expected, str)
}
}
tree.Add(c0100)
{
expected := "SB(Preference = 0, NumSuccessfulPolls[0] = 1, NumSuccessfulPolls[1] = 0, SF = SF(Preference = 0, Confidence = 1, Finalized = false)) Bit = 0\n"+
" SB(Preference = 0, NumSuccessfulPolls[0] = 1, NumSuccessfulPolls[1] = 0, SF = SF(Preference = 0, Confidence = 1, Finalized = false)) Bit = 2\n"+
" SB(NumSuccessfulPolls = 1, Confidence = 1, Finalized = true) Bits = [3, 256)\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [3, 256)\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [1, 256)"
if pref := tree.Preference(); !c0000.Equals(pref) {
t.Fatalf("Wrong preference. Expected %s got %s", c0000, pref)
} else if tree.Finalized() {
t.Fatalf("Finalized too early")
} else if str := tree.String(); expected != str {
t.Fatalf("Wrong string. Expected:\n%s\ngot:\n%s", expected, str)
}
}
c0100Bag := ids.Bag{}
c0100Bag.Add(c0100)
tree.RecordPoll(c0100Bag)
{
expected := "SB(Preference = 0, NumSuccessfulPolls[0] = 1, NumSuccessfulPolls[1] = 0, SF = SF(Preference = 0, Confidence = 0, Finalized = false)) Bit = 2\n"+
" SB(NumSuccessfulPolls = 1, Confidence = 1, Finalized = true) Bits = [3, 256)\n"+
" SB(NumSuccessfulPolls = 0, Confidence = 0, Finalized = false) Bits = [3, 256)"
if pref := tree.Preference(); !c0000.Equals(pref) {
t.Fatalf("Wrong preference. Expected %s got %s", c0000, pref)
} else if tree.Finalized() {
t.Fatalf("Finalized too early")
} else if str := tree.String(); expected != str {
t.Fatalf("Wrong string. Expected:\n%s\ngot:\n%s", expected, str)
}
}
}

View File

@ -171,7 +171,7 @@ func (l *Log) format(level Level, format string, args ...interface{}) string {
return fmt.Sprintf("%s[%s]%s %s\n", return fmt.Sprintf("%s[%s]%s %s\n",
level, level,
time.Now().Format("01-02|15:04:05.000"), time.Now().Format("01-02|15:04:05"),
prefix, prefix,
text) text)
} }

View File

@ -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) { func (vm *VM) IssueTx(b []byte, onDecide func(choices.Status)) (ids.ID, error) {
tx, err := vm.parseTx(b) tx, err := vm.parseTx(b)
if err != nil { if err != nil {

View File

@ -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) { func TestIssueDependentTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t) genesisBytes := BuildGenesisTest(t)

View File

@ -770,6 +770,8 @@ func (service *Service) signAddNonDefaultSubnetValidatorTx(tx *addNonDefaultSubn
return nil, errors.New("no place for key to sign") return nil, errors.New("no place for key to sign")
} }
crypto.SortSECP2561RSigs(tx.ControlSigs)
return tx, nil return tx, nil
} }

View File

@ -20,31 +20,42 @@ var (
// Keychain is a collection of keys that can be used to spend utxos // Keychain is a collection of keys that can be used to spend utxos
type Keychain struct { type Keychain struct {
factory crypto.FactorySECP256K1R
networkID uint32 networkID uint32
chainID ids.ID 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 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 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 Keys []*crypto.PrivateKeySECP256K1R
} }
// NewKeychain creates a new keychain for a chain // NewKeychain creates a new keychain for a chain
func NewKeychain(networkID uint32, chainID ids.ID) *Keychain { func NewKeychain(networkID uint32, chainID ids.ID) *Keychain {
return &Keychain{ return &Keychain{
networkID: networkID,
chainID: chainID, chainID: chainID,
keyMap: make(map[[20]byte]int), keyMap: make(map[[20]byte]int),
} }
} }
// New returns a newly generated private key // New returns a newly generated private key
func (kc *Keychain) New() *crypto.PrivateKeySECP256K1R { func (kc *Keychain) New() (*crypto.PrivateKeySECP256K1R, error) {
factory := &crypto.FactorySECP256K1R{} skGen, err := kc.factory.NewPrivateKey()
if err != nil {
skGen, _ := factory.NewPrivateKey() return nil, err
}
sk := skGen.(*crypto.PrivateKeySECP256K1R) sk := skGen.(*crypto.PrivateKeySECP256K1R)
kc.Add(sk) kc.Add(sk)
return sk return sk, nil
} }
// Add a new key to the key chain // Add a new key to the key chain

View File

@ -20,29 +20,35 @@ var (
// Keychain is a collection of keys that can be used to spend utxos // Keychain is a collection of keys that can be used to spend utxos
type Keychain struct { 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()) // Key: The id of a private key (namely, [privKey].PublicKey().Address().Key())
// Value: The index in Keys of that private key // Value: The index in Keys of that private key
keyMap map[[20]byte]int keyMap map[[20]byte]int
// Each element is an address controlled by a key in [Keys] // 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 Addrs ids.ShortSet
// List of keys this keychain manages // List of keys this keychain manages
// This can be used to iterate over. It should not be modified externally.
Keys []*crypto.PrivateKeySECP256K1R Keys []*crypto.PrivateKeySECP256K1R
} }
func (kc *Keychain) init() { // NewKeychain creates a new keychain for a chain
if kc.keyMap == nil { func NewKeychain(networkID uint32, chainID ids.ID) *Keychain {
kc.keyMap = make(map[[20]byte]int) return &Keychain{
networkID: networkID,
chainID: chainID,
keyMap: make(map[[20]byte]int),
} }
} }
// Add a new key to the key chain. // Add a new key to the key chain.
// If [key] is already in the keychain, does nothing. // 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] addr := key.PublicKey().Address() // The address controlled by [key]
addrHash := addr.Key() addrHash := addr.Key()
if _, ok := kc.keyMap[addrHash]; !ok { 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. // 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 { if i, ok := kc.keyMap[id.Key()]; ok {
return kc.Keys[i], true 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 // 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. // New returns a newly generated private key.
// The key and the address it controls are added to // The key and the address it controls are added to
// [kc.Keys] and [kc.Addrs], respectively // [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 := kc.factory.NewPrivateKey()
skGen, err := factory.NewPrivateKey()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -84,8 +86,8 @@ func (kc *Keychain) New() (*crypto.PrivateKeySECP256K1R, error) {
// Spend attempts to create an input // 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{ builder := Builder{
NetworkID: 0, NetworkID: kc.networkID,
ChainID: ids.Empty, ChainID: kc.chainID,
} }
switch out := utxo.Out().(type) { switch out := utxo.Out().(type) {
@ -148,8 +150,8 @@ func (kc *Keychain) GetSigsAndKeys(addresses []ids.ShortID, threshold int) ([]*S
sigs := []*Sig{} sigs := []*Sig{}
keys := []*crypto.PrivateKeySECP256K1R{} keys := []*crypto.PrivateKeySECP256K1R{}
builder := Builder{ builder := Builder{
NetworkID: 0, NetworkID: kc.networkID,
ChainID: ids.Empty, ChainID: kc.chainID,
} }
for i := uint32(0); i < uint32(len(addresses)) && len(keys) < threshold; i++ { for i := uint32(0); i < uint32(len(addresses)) && len(keys) < threshold; i++ {
if key, exists := kc.Get(addresses[i]); exists { if key, exists := kc.Get(addresses[i]); exists {

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 // Add all of the keys in [fromPKs] to a keychain
keychain := Keychain{} keychain := NewKeychain(vm.ctx.NetworkID, vm.ctx.ChainID)
factory := crypto.FactorySECP256K1R{} factory := crypto.FactorySECP256K1R{}
cb58 := formatting.CB58{} cb58 := formatting.CB58{}
for _, fpk := range fromPKs { for _, fpk := range fromPKs {
@ -359,7 +359,7 @@ func (vm *VM) Send(amount uint64, assetID, toAddrStr string, fromPKs []string) (
ChainID: vm.ctx.ChainID, ChainID: vm.ctx.ChainID,
} }
currentTime := vm.clock.Unix() 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 { if err != nil {
return "", err return "", err
} }

17
xputtest/README.md Normal file
View File

@ -0,0 +1,17 @@
# Throughput testing
A throughput test is run in two parts. First a network must be running with at least one of the nodes running a throughput server. To start a throughput server when running a node the `--xput-server-enabled=true` flag should be passed.
An example single node network can be started with:
```sh
./build/ava --public-ip=127.0.0.1 --xput-server-port=9652 --xput-server-enabled=true --db-enabled=false --staking-tls-enabled=false --snow-sample-size=1 --snow-quorum-size=1
```
The thoughput node can be started with:
```sh
./build/xputtest --ip=127.0.0.1 --port=9652 --sp-chain
```
The above example with run a throughput test on the simple payment chain. Tests can be run with `--sp-dag` to run throughput tests on the simple payment dag. Tests can be run with `--avm` to run throughput tests on the AVA virtual machine.

View File

@ -19,12 +19,10 @@ import (
"github.com/ava-labs/gecko/xputtest/avmwallet" "github.com/ava-labs/gecko/xputtest/avmwallet"
) )
func (n *network) benchmarkAVM(genesisState *platformvm.Genesis) { // benchmark an instance of the avm
avmChain := genesisState.Chains[0] func (n *network) benchmarkAVM(chain *platformvm.CreateChainTx) {
n.log.AssertTrue(avmChain.ChainName == "AVM", "wrong chain name") genesisBytes := chain.GenesisData
genesisBytes := avmChain.GenesisData wallet, err := avmwallet.NewWallet(n.log, n.networkID, chain.ID(), config.AvaTxFee)
wallet, err := avmwallet.NewWallet(n.networkID, avmChain.ID(), config.AvaTxFee)
n.log.AssertNoError(err) n.log.AssertNoError(err)
cb58 := formatting.CB58{} cb58 := formatting.CB58{}
@ -56,9 +54,10 @@ func (n *network) benchmarkAVM(genesisState *platformvm.Genesis) {
n.log.AssertNoError(wallet.GenerateTxs(config.NumTxs, assetID)) 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) { func (n *network) IssueAVM(chainID ids.ID, assetID ids.ID, wallet *avmwallet.Wallet) {
n.log.Debug("Issuing with %d", wallet.Balance(assetID)) n.log.Debug("Issuing with %d", wallet.Balance(assetID))
numAccepted := 0 numAccepted := 0
@ -66,11 +65,15 @@ func (n *network) IssueAVM(chainID ids.ID, assetID ids.ID, wallet *avmwallet.Wal
n.decided <- ids.ID{} n.decided <- ids.ID{}
// track the last second of transactions
meter := timer.TimedMeter{Duration: time.Second} meter := timer.TimedMeter{Duration: time.Second}
for d := range n.decided { for d := range n.decided {
// display the TPS every 1000 txs
if numAccepted%1000 == 0 { if numAccepted%1000 == 0 {
n.log.Info("TPS: %d", meter.Ticks()) n.log.Info("TPS: %d", meter.Ticks())
} }
// d is the ID of the tx that was accepted
if !d.IsZero() { if !d.IsZero() {
meter.Tick() meter.Tick()
n.log.Debug("Finalized %s", d) n.log.Debug("Finalized %s", d)
@ -78,10 +81,12 @@ func (n *network) IssueAVM(chainID ids.ID, assetID ids.ID, wallet *avmwallet.Wal
numPending-- numPending--
} }
// Issue all the txs that we can right now
for numPending < config.MaxOutstandingTxs && wallet.Balance(assetID) > 0 && numAccepted+numPending < config.NumTxs { for numPending < config.MaxOutstandingTxs && wallet.Balance(assetID) > 0 && numAccepted+numPending < config.NumTxs {
tx := wallet.NextTx() tx := wallet.NextTx()
n.log.AssertTrue(tx != nil, "Tx creation failed") n.log.AssertTrue(tx != nil, "Tx creation failed")
// send the IssueTx message
it, err := n.build.IssueTx(chainID, tx.Bytes()) it, err := n.build.IssueTx(chainID, tx.Bytes())
n.log.AssertNoError(err) n.log.AssertNoError(err)
ds := it.DataStream() ds := it.DataStream()
@ -97,8 +102,11 @@ func (n *network) IssueAVM(chainID ids.ID, assetID ids.ID, wallet *avmwallet.Wal
numPending++ numPending++
n.log.Debug("Sent tx, pending = %d, accepted = %d", numPending, numAccepted) n.log.Debug("Sent tx, pending = %d, accepted = %d", numPending, numAccepted)
} }
// If we are done issuing txs, return from the function
if numAccepted+numPending >= config.NumTxs { if numAccepted+numPending >= config.NumTxs {
n.log.Info("done with test") n.log.Info("done with test")
net.ec.Stop()
return return
} }
} }

View File

@ -13,8 +13,12 @@ import (
// UTXOSet ... // UTXOSet ...
type UTXOSet struct { 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 utxoMap map[[32]byte]int
// List of UTXOs in this set
// This can be used to iterate over. It should not be modified externally.
UTXOs []*avm.UTXO UTXOs []*avm.UTXO
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing" "github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/utils/math" "github.com/ava-labs/gecko/utils/math"
"github.com/ava-labs/gecko/utils/timer" "github.com/ava-labs/gecko/utils/timer"
"github.com/ava-labs/gecko/utils/wrappers" "github.com/ava-labs/gecko/utils/wrappers"
@ -25,10 +26,14 @@ import (
type Wallet struct { type Wallet struct {
networkID uint32 networkID uint32
chainID ids.ID chainID ids.ID
clock timer.Clock clock timer.Clock
codec codec.Codec codec codec.Codec
log logging.Logger
keychain *secp256k1fx.Keychain // Mapping from public address to the SigningKeys keychain *secp256k1fx.Keychain // Mapping from public address to the SigningKeys
utxoSet *UTXOSet // Mapping from utxoIDs to UTXOs utxoSet *UTXOSet // Mapping from utxoIDs to UTXOs
balance map[[32]byte]uint64 balance map[[32]byte]uint64
txFee uint64 txFee uint64
@ -37,7 +42,7 @@ type Wallet struct {
} }
// NewWallet returns a new Wallet // NewWallet returns a new Wallet
func NewWallet(networkID uint32, chainID ids.ID, txFee uint64) (*Wallet, error) { func NewWallet(log logging.Logger, networkID uint32, chainID ids.ID, txFee uint64) (*Wallet, error) {
c := codec.NewDefault() c := codec.NewDefault()
errs := wrappers.Errs{} errs := wrappers.Errs{}
errs.Add( errs.Add(
@ -54,6 +59,7 @@ func NewWallet(networkID uint32, chainID ids.ID, txFee uint64) (*Wallet, error)
networkID: networkID, networkID: networkID,
chainID: chainID, chainID: chainID,
codec: c, codec: c,
log: log,
keychain: secp256k1fx.NewKeychain(), keychain: secp256k1fx.NewKeychain(),
utxoSet: &UTXOSet{}, utxoSet: &UTXOSet{},
balance: make(map[[32]byte]uint64), balance: make(map[[32]byte]uint64),
@ -249,10 +255,17 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) (
// Generate them all on test initialization so tx generation is not bottleneck // Generate them all on test initialization so tx generation is not bottleneck
// in testing // in testing
func (w *Wallet) GenerateTxs(numTxs int, assetID ids.ID) error { func (w *Wallet) GenerateTxs(numTxs int, assetID ids.ID) error {
w.log.Info("Generating %d transactions", numTxs)
ctx := snow.DefaultContextTest() ctx := snow.DefaultContextTest()
ctx.NetworkID = w.networkID ctx.NetworkID = w.networkID
ctx.ChainID = w.chainID ctx.ChainID = w.chainID
frequency := numTxs / 50
if frequency > 1000 {
frequency = 1000
}
w.txs = make([]*avm.Tx, numTxs) w.txs = make([]*avm.Tx, numTxs)
for i := 0; i < numTxs; i++ { for i := 0; i < numTxs; i++ {
addr, err := w.CreateAddress() addr, err := w.CreateAddress()
@ -271,8 +284,14 @@ func (w *Wallet) GenerateTxs(numTxs int, assetID ids.ID) error {
w.AddUTXO(utxo) w.AddUTXO(utxo)
} }
if numGenerated := i + 1; numGenerated%frequency == 0 {
w.log.Info("Generated %d out of %d transactions", numGenerated, numTxs)
}
w.txs[i] = tx w.txs[i] = tx
} }
w.log.Info("Finished generating %d transactions", numTxs)
return nil return nil
} }

View File

@ -11,15 +11,15 @@ import (
"github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting" "github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/utils/units" "github.com/ava-labs/gecko/utils/units"
"github.com/ava-labs/gecko/vms/avm" "github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/platformvm"
"github.com/ava-labs/gecko/vms/secp256k1fx" "github.com/ava-labs/gecko/vms/secp256k1fx"
) )
func TestNewWallet(t *testing.T) { func TestNewWallet(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0) w, err := NewWallet(logging.NoLog{}, 12345, chainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -30,7 +30,7 @@ func TestNewWallet(t *testing.T) {
func TestWalletGetAddress(t *testing.T) { func TestWalletGetAddress(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0) w, err := NewWallet(logging.NoLog{}, 12345, chainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -46,7 +46,7 @@ func TestWalletGetAddress(t *testing.T) {
func TestWalletGetMultipleAddresses(t *testing.T) { func TestWalletGetMultipleAddresses(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0) w, err := NewWallet(logging.NoLog{}, 12345, chainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -66,7 +66,7 @@ func TestWalletGetMultipleAddresses(t *testing.T) {
func TestWalletEmptyBalance(t *testing.T) { func TestWalletEmptyBalance(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0) w, err := NewWallet(logging.NoLog{}, 12345, chainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -78,7 +78,7 @@ func TestWalletEmptyBalance(t *testing.T) {
func TestWalletAddUTXO(t *testing.T) { func TestWalletAddUTXO(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0) w, err := NewWallet(logging.NoLog{}, 12345, chainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -100,7 +100,7 @@ func TestWalletAddUTXO(t *testing.T) {
func TestWalletAddInvalidUTXO(t *testing.T) { func TestWalletAddInvalidUTXO(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0) w, err := NewWallet(logging.NoLog{}, 12345, chainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -119,7 +119,7 @@ func TestWalletAddInvalidUTXO(t *testing.T) {
func TestWalletCreateTx(t *testing.T) { func TestWalletCreateTx(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0) w, err := NewWallet(logging.NoLog{}, 12345, chainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -169,7 +169,7 @@ func TestWalletCreateTx(t *testing.T) {
func TestWalletImportKey(t *testing.T) { func TestWalletImportKey(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0) w, err := NewWallet(logging.NoLog{}, 12345, chainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -194,7 +194,7 @@ func TestWalletImportKey(t *testing.T) {
func TestWalletString(t *testing.T) { func TestWalletString(t *testing.T) {
chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) chainID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(12345, chainID, 0) w, err := NewWallet(logging.NoLog{}, 12345, chainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -226,7 +226,7 @@ func TestWalletWithGenesis(t *testing.T) {
ctx.NetworkID = 12345 ctx.NetworkID = 12345
ctx.ChainID = ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) ctx.ChainID = ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
w, err := NewWallet(ctx.NetworkID, ctx.ChainID, 0) w, err := NewWallet(logging.NoLog{}, ctx.NetworkID, ctx.ChainID, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -245,20 +245,7 @@ func TestWalletWithGenesis(t *testing.T) {
w.ImportKey(sk.(*crypto.PrivateKeySECP256K1R)) w.ImportKey(sk.(*crypto.PrivateKeySECP256K1R))
} }
platformGenesisBytes := genesis.Genesis(genesis.LocalID) avmChain := genesis.VMGenesis(ctx.NetworkID, avm.ID)
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 genesisBytes := avmChain.GenesisData
genesis := avm.Genesis{} genesis := avm.Genesis{}
@ -282,8 +269,8 @@ func TestWalletWithGenesis(t *testing.T) {
assetID := genesisTx.ID() assetID := genesisTx.ID()
if balance := w.Balance(assetID); balance != 45*units.MegaAva { if balance := w.Balance(assetID); balance == 0 {
t.Fatalf("balance of %d was expected but got %d", 45*units.MegaAva, balance) t.Fatalf("expected a positive balance")
} }
for i := 1; i <= 1000; i++ { for i := 1; i <= 1000; i++ {

View File

@ -10,6 +10,7 @@ import (
"github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/vms/spchainvm" "github.com/ava-labs/gecko/vms/spchainvm"
) )
@ -17,6 +18,9 @@ import (
type Wallet struct { type Wallet struct {
networkID uint32 networkID uint32
chainID ids.ID chainID ids.ID
log logging.Logger
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 accountSet map[[20]byte]spchainvm.Account // Mapping from addresses to accounts
balance uint64 balance uint64
@ -25,17 +29,24 @@ type Wallet struct {
} }
// NewWallet ... // NewWallet ...
func NewWallet(networkID uint32, chainID ids.ID) Wallet { func NewWallet(log logging.Logger, networkID uint32, chainID ids.ID) *Wallet {
return Wallet{ return &Wallet{
networkID: networkID, networkID: networkID,
chainID: chainID, chainID: chainID,
log: log,
keychain: spchainvm.NewKeychain(networkID, chainID), keychain: spchainvm.NewKeychain(networkID, chainID),
accountSet: make(map[[20]byte]spchainvm.Account), accountSet: make(map[[20]byte]spchainvm.Account),
} }
} }
// CreateAddress returns a brand new address! Ready to receive funds! // 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 // 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) }
@ -56,65 +67,36 @@ func (w *Wallet) Balance() uint64 { return w.balance }
// Generate them all on test initialization so tx generation is not bottleneck // Generate them all on test initialization so tx generation is not bottleneck
// in testing // in testing
func (w *Wallet) GenerateTxs(numTxs int) error { func (w *Wallet) GenerateTxs(numTxs int) error {
w.log.Info("Generating %d transactions", numTxs)
ctx := snow.DefaultContextTest() ctx := snow.DefaultContextTest()
ctx.NetworkID = w.networkID ctx.NetworkID = w.networkID
ctx.ChainID = w.chainID ctx.ChainID = w.chainID
frequency := numTxs / 50
if frequency > 1000 {
frequency = 1000
}
w.txs = make([]*spchainvm.Tx, numTxs) w.txs = make([]*spchainvm.Tx, numTxs)
for i := 0; i < numTxs; { for i := range w.txs {
for _, account := range w.accountSet { tx, err := w.MakeTx()
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 { if err != nil {
return err return err
} }
newAccount, err := sendAccount.Receive(tx, ctx) if numGenerated := i + 1; numGenerated%frequency == 0 {
if err != nil { w.log.Info("Generated %d out of %d transactions", numGenerated, numTxs)
return err
} }
w.accountSet[accountID.Key()] = newAccount
w.txs[i] = tx w.txs[i] = tx
i++
}
} }
w.log.Info("Finished generating %d transactions", numTxs)
return nil 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 // NextTx returns the next tx to be sent as part of xput test
func (w *Wallet) NextTx() *spchainvm.Tx { func (w *Wallet) NextTx() *spchainvm.Tx {
if len(w.txs) == 0 { if len(w.txs) == 0 {
@ -125,6 +107,35 @@ func (w *Wallet) NextTx() *spchainvm.Tx {
return 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 { func (w Wallet) String() string {
return fmt.Sprintf( return fmt.Sprintf(
"Keychain:\n"+ "Keychain:\n"+

View File

@ -13,8 +13,12 @@ import (
// UTXOSet ... // UTXOSet ...
type UTXOSet struct { 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 utxoMap map[[32]byte]int
// List of UTXOs in this set
// This can be used to iterate over. It should not be modified externally.
UTXOs []*spdagvm.UTXO UTXOs []*spdagvm.UTXO
} }

View File

@ -25,11 +25,11 @@ type Wallet struct {
} }
// NewWallet returns a new Wallet // NewWallet returns a new Wallet
func NewWallet(networkID uint32, chainID ids.ID, txFee uint64) Wallet { func NewWallet(networkID uint32, chainID ids.ID, txFee uint64) *Wallet {
return Wallet{ return &Wallet{
networkID: networkID, networkID: networkID,
chainID: chainID, chainID: chainID,
keychain: &spdagvm.Keychain{}, keychain: spdagvm.NewKeychain(networkID, chainID),
utxoSet: &UTXOSet{}, utxoSet: &UTXOSet{},
txFee: txFee, txFee: txFee,
} }

View File

@ -16,7 +16,9 @@ import (
"github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/logging" "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() { func main() {
@ -24,6 +26,7 @@ func main() {
fmt.Printf("Failed to parse arguments: %s\n", err) fmt.Printf("Failed to parse arguments: %s\n", err)
} }
// set up logging
config.LoggingConfig.Directory = path.Join(config.LoggingConfig.Directory, "client") config.LoggingConfig.Directory = path.Join(config.LoggingConfig.Directory, "client")
log, err := logging.New(config.LoggingConfig) log, err := logging.New(config.LoggingConfig)
if err != nil { if err != nil {
@ -33,6 +36,7 @@ func main() {
defer log.Stop() defer log.Stop()
// initialize state based on CLI args
net.log = log net.log = log
crypto.EnableCrypto = config.EnableCrypto crypto.EnableCrypto = config.EnableCrypto
net.decided = make(chan ids.ID, config.MaxOutstandingTxs) net.decided = make(chan ids.ID, config.MaxOutstandingTxs)
@ -42,11 +46,13 @@ func main() {
return return
} }
// Init the network
log.AssertNoError(net.Initialize()) log.AssertNoError(net.Initialize())
net.net.Start() net.net.Start()
defer net.net.Stop() defer net.net.Stop()
// connect to the node
serr := salticidae.NewError() serr := salticidae.NewError()
remoteIP := salticidae.NewNetAddrFromIPPortString(config.RemoteIP.String(), true, &serr) remoteIP := salticidae.NewNetAddrFromIPPortString(config.RemoteIP.String(), true, &serr)
if code := serr.GetCode(); code != 0 { if code := serr.GetCode(); code != 0 {
@ -60,6 +66,7 @@ func main() {
return return
} }
// start a cpu profile
file, gErr := os.Create("cpu_client.profile") file, gErr := os.Create("cpu_client.profile")
log.AssertNoError(gErr) log.AssertNoError(gErr)
gErr = pprof.StartCPUProfile(file) gErr = pprof.StartCPUProfile(file)
@ -71,22 +78,19 @@ func main() {
net.networkID = config.NetworkID net.networkID = config.NetworkID
platformGenesisBytes := genesis.Genesis(net.networkID) // start the benchmark we want to run
genesisState := &platformvm.Genesis{}
log.AssertNoError(platformvm.Codec.Unmarshal(platformGenesisBytes, genesisState))
log.AssertNoError(genesisState.Initialize())
switch config.Chain { switch config.Chain {
case spChain: case spChain:
net.benchmarkSPChain(genesisState) net.benchmarkSPChain(genesis.VMGenesis(config.NetworkID, spchainvm.ID))
case spDAG: case spDAG:
net.benchmarkSPDAG(genesisState) net.benchmarkSPDAG(genesis.VMGenesis(config.NetworkID, spdagvm.ID))
case avmDAG: case avmDAG:
net.benchmarkAVM(genesisState) net.benchmarkAVM(genesis.VMGenesis(config.NetworkID, avm.ID))
default: default:
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 return
} }
// start processing network messages
net.ec.Dispatch() net.ec.Dispatch()
} }

View File

@ -19,12 +19,10 @@ import (
"github.com/ava-labs/gecko/xputtest/chainwallet" "github.com/ava-labs/gecko/xputtest/chainwallet"
) )
func (n *network) benchmarkSPChain(genesisState *platformvm.Genesis) { // benchmark an instance of the sp chain
spchainChain := genesisState.Chains[3] func (n *network) benchmarkSPChain(chain *platformvm.CreateChainTx) {
n.log.AssertTrue(spchainChain.ChainName == "Simple Chain Payments", "wrong chain name") genesisBytes := chain.GenesisData
genesisBytes := spchainChain.GenesisData wallet := chainwallet.NewWallet(n.log, n.networkID, chain.ID())
wallet := chainwallet.NewWallet(n.networkID, spchainChain.ID())
codec := spchainvm.Codec{} codec := spchainvm.Codec{}
accounts, err := codec.UnmarshalGenesis(genesisBytes) accounts, err := codec.UnmarshalGenesis(genesisBytes)
@ -47,10 +45,10 @@ func (n *network) benchmarkSPChain(genesisState *platformvm.Genesis) {
n.log.AssertNoError(wallet.GenerateTxs(config.NumTxs)) 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()) n.log.Debug("Issuing with %d", wallet.Balance())
numAccepted := 0 numAccepted := 0
numPending := 0 numPending := 0
@ -90,6 +88,7 @@ func (n *network) IssueSPChain(chainID ids.ID, wallet chainwallet.Wallet) {
} }
if numAccepted+numPending >= config.NumTxs { if numAccepted+numPending >= config.NumTxs {
n.log.Info("done with test") n.log.Info("done with test")
net.ec.Stop()
return return
} }
} }

View File

@ -19,12 +19,10 @@ import (
"github.com/ava-labs/gecko/xputtest/dagwallet" "github.com/ava-labs/gecko/xputtest/dagwallet"
) )
func (n *network) benchmarkSPDAG(genesisState *platformvm.Genesis) { // benchmark an instance of the sp dag
spDAGChain := genesisState.Chains[2] func (n *network) benchmarkSPDAG(chain *platformvm.CreateChainTx) {
n.log.AssertTrue(spDAGChain.ChainName == "Simple DAG Payments", "wrong chain name") genesisBytes := chain.GenesisData
genesisBytes := spDAGChain.GenesisData wallet := dagwallet.NewWallet(n.networkID, chain.ID(), config.AvaTxFee)
wallet := dagwallet.NewWallet(n.networkID, spDAGChain.ID(), config.AvaTxFee)
codec := spdagvm.Codec{} codec := spdagvm.Codec{}
tx, err := codec.UnmarshalTx(genesisBytes) tx, err := codec.UnmarshalTx(genesisBytes)
@ -43,10 +41,11 @@ func (n *network) benchmarkSPDAG(genesisState *platformvm.Genesis) {
wallet.AddUTXO(utxo) 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") n.log.Info("starting avalanche benchmark")
pending := make(map[[32]byte]*spdagvm.Tx) pending := make(map[[32]byte]*spdagvm.Tx)
canAdd := []*spdagvm.Tx{} canAdd := []*spdagvm.Tx{}