Merge branch 'atomic' into nftfx

This commit is contained in:
StephenButtolph 2020-03-29 14:13:57 -04:00
commit ec83ca4a1a
97 changed files with 5381 additions and 2170 deletions

View File

@ -0,0 +1,28 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package atomic
import (
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/ids"
)
// BlockchainSharedMemory provides the API for a blockchain to interact with
// shared memory of another blockchain
type BlockchainSharedMemory struct {
blockchainID ids.ID
sm *SharedMemory
}
// GetDatabase returns and locks the provided DB
func (bsm *BlockchainSharedMemory) GetDatabase(id ids.ID) database.Database {
sharedID := bsm.sm.sharedID(id, bsm.blockchainID)
return bsm.sm.GetDatabase(sharedID)
}
// ReleaseDatabase unlocks the provided DB
func (bsm *BlockchainSharedMemory) ReleaseDatabase(id ids.ID) {
sharedID := bsm.sm.sharedID(id, bsm.blockchainID)
bsm.sm.ReleaseDatabase(sharedID)
}

View File

@ -0,0 +1,34 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package atomic
import (
"bytes"
"testing"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/utils/logging"
)
func TestBlockchainSharedMemory(t *testing.T) {
sm := SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
bsm0 := sm.NewBlockchainSharedMemory(blockchainID0)
bsm1 := sm.NewBlockchainSharedMemory(blockchainID1)
sharedDB0 := bsm0.GetDatabase(blockchainID1)
if err := sharedDB0.Put([]byte{1}, []byte{2}); err != nil {
t.Fatal(err)
}
bsm0.ReleaseDatabase(blockchainID1)
sharedDB1 := bsm1.GetDatabase(blockchainID0)
if value, err := sharedDB1.Get([]byte{1}); err != nil {
t.Fatal(err)
} else if !bytes.Equal(value, []byte{2}) {
t.Fatalf("database.Get Returned: 0x%x ; Expected: 0x%x", value, []byte{2})
}
bsm1.ReleaseDatabase(blockchainID0)
}

105
chains/atomic/memory.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 atomic
import (
"bytes"
"sync"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/prefixdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/vms/components/codec"
)
type rcLock struct {
lock sync.Mutex
count int
}
// SharedMemory is the interface for shared memory inside a subnet
type SharedMemory struct {
lock sync.Mutex
log logging.Logger
codec codec.Codec
locks map[[32]byte]*rcLock
db database.Database
}
// Initialize the SharedMemory
func (sm *SharedMemory) Initialize(log logging.Logger, db database.Database) {
sm.log = log
sm.codec = codec.NewDefault()
sm.locks = make(map[[32]byte]*rcLock)
sm.db = db
}
// NewBlockchainSharedMemory returns a new BlockchainSharedMemory
func (sm *SharedMemory) NewBlockchainSharedMemory(id ids.ID) *BlockchainSharedMemory {
return &BlockchainSharedMemory{
blockchainID: id,
sm: sm,
}
}
// GetDatabase returns and locks the provided DB
func (sm *SharedMemory) GetDatabase(id ids.ID) database.Database {
lock := sm.makeLock(id)
lock.Lock()
return prefixdb.New(id.Bytes(), sm.db)
}
// ReleaseDatabase unlocks the provided DB
func (sm *SharedMemory) ReleaseDatabase(id ids.ID) {
lock := sm.releaseLock(id)
lock.Unlock()
}
func (sm *SharedMemory) makeLock(id ids.ID) *sync.Mutex {
sm.lock.Lock()
defer sm.lock.Unlock()
key := id.Key()
rc, exists := sm.locks[key]
if !exists {
rc = &rcLock{}
sm.locks[key] = rc
}
rc.count++
return &rc.lock
}
func (sm *SharedMemory) releaseLock(id ids.ID) *sync.Mutex {
sm.lock.Lock()
defer sm.lock.Unlock()
key := id.Key()
rc, exists := sm.locks[key]
if !exists {
panic("Attemping to free an unknown lock")
}
rc.count--
if rc.count == 0 {
delete(sm.locks, key)
}
return &rc.lock
}
// sharedID calculates the ID of the shared memory space
func (sm *SharedMemory) sharedID(id1, id2 ids.ID) ids.ID {
idKey1 := id1.Key()
idKey2 := id2.Key()
if bytes.Compare(idKey1[:], idKey2[:]) == 1 {
idKey1, idKey2 = idKey2, idKey1
}
combinedBytes, err := sm.codec.Marshal([2][32]byte{idKey1, idKey2})
sm.log.AssertNoError(err)
return ids.NewID(hashing.ComputeHash256Array(combinedBytes))
}

View File

@ -0,0 +1,69 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package atomic
import (
"testing"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/logging"
)
var (
blockchainID0 = ids.Empty.Prefix(0)
blockchainID1 = ids.Empty.Prefix(1)
)
func TestSharedMemorySharedID(t *testing.T) {
sm := SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
sharedID0 := sm.sharedID(blockchainID0, blockchainID1)
sharedID1 := sm.sharedID(blockchainID1, blockchainID0)
if !sharedID0.Equals(sharedID1) {
t.Fatalf("SharedMemory.sharedID should be communitive")
}
}
func TestSharedMemoryMakeReleaseLock(t *testing.T) {
sm := SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
sharedID := sm.sharedID(blockchainID0, blockchainID1)
lock0 := sm.makeLock(sharedID)
if lock1 := sm.makeLock(sharedID); lock0 != lock1 {
t.Fatalf("SharedMemory.makeLock should have returned the same lock")
}
sm.releaseLock(sharedID)
if lock2 := sm.makeLock(sharedID); lock0 != lock2 {
t.Fatalf("SharedMemory.makeLock should have returned the same lock")
}
sm.releaseLock(sharedID)
sm.releaseLock(sharedID)
if lock3 := sm.makeLock(sharedID); lock0 == lock3 {
t.Fatalf("SharedMemory.releaseLock should have returned freed the lock")
}
sm.releaseLock(sharedID)
}
func TestSharedMemoryUnknownFree(t *testing.T) {
sm := SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
sharedID := sm.sharedID(blockchainID0, blockchainID1)
defer func() {
if recover() == nil {
t.Fatalf("Should have panicked due to an unknown free")
}
}()
sm.releaseLock(sharedID)
}

21
chains/atomic/writer.go Normal file
View File

@ -0,0 +1,21 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package atomic
import (
"github.com/ava-labs/gecko/database"
)
// WriteAll assumes all batches have the same underlying database. Batches
// should not be modified after being passed to this function.
func WriteAll(baseBatch database.Batch, batches ...database.Batch) error {
baseBatch = baseBatch.Inner()
for _, batch := range batches {
batch = batch.Inner()
if err := batch.Replay(baseBatch); err != nil {
return err
}
}
return baseBatch.Write()
}

View File

@ -0,0 +1,61 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package atomic
import (
"bytes"
"testing"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/database/prefixdb"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/utils/logging"
)
func TestWriteAll(t *testing.T) {
baseDB := memdb.New()
prefixedDBChain := prefixdb.New([]byte{0}, baseDB)
prefixedDBSharedMemory := prefixdb.New([]byte{1}, baseDB)
sm := SharedMemory{}
sm.Initialize(logging.NoLog{}, prefixedDBSharedMemory)
sharedID := sm.sharedID(blockchainID0, blockchainID1)
sharedDB := sm.GetDatabase(sharedID)
writeDB0 := versiondb.New(prefixedDBChain)
writeDB1 := versiondb.New(sharedDB)
defer sm.ReleaseDatabase(sharedID)
if err := writeDB0.Put([]byte{1}, []byte{2}); err != nil {
t.Fatal(err)
}
if err := writeDB1.Put([]byte{2}, []byte{3}); err != nil {
t.Fatal(err)
}
batch0, err := writeDB0.CommitBatch()
if err != nil {
t.Fatal(err)
}
batch1, err := writeDB1.CommitBatch()
if err != nil {
t.Fatal(err)
}
if err := WriteAll(batch0, batch1); err != nil {
t.Fatal(err)
}
if value, err := prefixedDBChain.Get([]byte{1}); err != nil {
t.Fatal(err)
} else if !bytes.Equal(value, []byte{2}) {
t.Fatalf("database.Get Returned: 0x%x ; Expected: 0x%x", value, []byte{2})
} else if value, err := sharedDB.Get([]byte{2}); err != nil {
t.Fatal(err)
} else if !bytes.Equal(value, []byte{3}) {
t.Fatalf("database.Get Returned: 0x%x ; Expected: 0x%x", value, []byte{3})
}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/ava-labs/gecko/api"
"github.com/ava-labs/gecko/api/keystore"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/prefixdb"
"github.com/ava-labs/gecko/ids"
@ -109,6 +110,7 @@ type manager struct {
awaiter Awaiter // Waits for required connections before running bootstrapping
server *api.Server // Handles HTTP API calls
keystore *keystore.Keystore
sharedMemory *atomic.SharedMemory
unblocked bool
blockedChains []ChainParameters
@ -135,6 +137,7 @@ func New(
awaiter Awaiter,
server *api.Server,
keystore *keystore.Keystore,
sharedMemory *atomic.SharedMemory,
) Manager {
timeoutManager := timeout.Manager{}
timeoutManager.Initialize(requestTimeout)
@ -159,6 +162,7 @@ func New(
awaiter: awaiter,
server: server,
keystore: keystore,
sharedMemory: sharedMemory,
}
m.Initialize()
return m
@ -246,6 +250,7 @@ func (m *manager) ForceCreateChain(chain ChainParameters) {
NodeID: m.nodeID,
HTTP: m.server,
Keystore: m.keystore.NewBlockchainKeyStore(chain.ID),
SharedMemory: m.sharedMemory.NewBlockchainSharedMemory(chain.ID),
BCLookup: m,
}
consensusParams := m.consensusParams

View File

@ -23,6 +23,11 @@ type Batch interface {
// Replay replays the batch contents.
Replay(w KeyValueWriter) error
// Inner returns a Batch writing to the inner database, if one exists. If
// this batch is already writing to the base DB, then itself should be
// returned.
Inner() Batch
}
// Batcher wraps the NewBatch method of a backing data store.

View File

@ -184,6 +184,9 @@ func (b *batch) Replay(w database.KeyValueWriter) error {
return updateError(replay.err)
}
// Inner returns itself
func (b *batch) Inner() database.Batch { return b }
type replayer struct {
writer database.KeyValueWriter
err error

View File

@ -208,6 +208,9 @@ func (b *batch) Replay(w database.KeyValueWriter) error {
return nil
}
// Inner returns itself
func (b *batch) Inner() database.Batch { return b }
type iterator struct {
initialized bool
keys []string

View File

@ -69,6 +69,9 @@ func (*Batch) Reset() {}
// Replay does nothing
func (*Batch) Replay(database.KeyValueWriter) error { return database.ErrClosed }
// Inner returns itself
func (b *Batch) Inner() database.Batch { return b }
// Iterator does nothing
type Iterator struct{ Err error }

View File

@ -17,6 +17,7 @@ var (
TestBatchDelete,
TestBatchReset,
TestBatchReplay,
TestBatchInner,
TestIterator,
TestIteratorStart,
TestIteratorPrefix,
@ -299,6 +300,62 @@ func TestBatchReplay(t *testing.T, db Database) {
}
}
// TestBatchInner ...
func TestBatchInner(t *testing.T, db Database) {
key1 := []byte("hello1")
value1 := []byte("world1")
key2 := []byte("hello2")
value2 := []byte("world2")
firstBatch := db.NewBatch()
if firstBatch == nil {
t.Fatalf("db.NewBatch returned nil")
}
if err := firstBatch.Put(key1, value1); err != nil {
t.Fatalf("Unexpected error on batch.Put: %s", err)
}
secondBatch := db.NewBatch()
if secondBatch == nil {
t.Fatalf("db.NewBatch returned nil")
}
if err := secondBatch.Put(key2, value2); err != nil {
t.Fatalf("Unexpected error on batch.Put: %s", err)
}
innerFirstBatch := firstBatch.Inner()
innerSecondBatch := secondBatch.Inner()
if err := innerFirstBatch.Replay(innerSecondBatch); err != nil {
t.Fatalf("Unexpected error on batch.Replay: %s", err)
}
if err := innerSecondBatch.Write(); err != nil {
t.Fatalf("Unexpected error on batch.Write: %s", err)
}
if has, err := db.Has(key1); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if !has {
t.Fatalf("db.Has unexpectedly returned false on key %s", key1)
} else if v, err := db.Get(key1); err != nil {
t.Fatalf("Unexpected error on db.Get: %s", err)
} else if !bytes.Equal(value1, v) {
t.Fatalf("db.Get: Returned: 0x%x ; Expected: 0x%x", v, value1)
} else if has, err := db.Has(key2); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if !has {
t.Fatalf("db.Has unexpectedly returned false on key %s", key2)
} else if v, err := db.Get(key2); err != nil {
t.Fatalf("Unexpected error on db.Get: %s", err)
} else if !bytes.Equal(value2, v) {
t.Fatalf("db.Get: Returned: 0x%x ; Expected: 0x%x", v, value2)
}
}
// TestIterator ...
func TestIterator(t *testing.T, db Database) {
key1 := []byte("hello1")

View File

@ -184,29 +184,55 @@ func (db *Database) Commit() error {
db.lock.Lock()
defer db.lock.Unlock()
if db.mem == nil {
return database.ErrClosed
batch, err := db.commitBatch()
if err != nil {
return err
}
if len(db.mem) == 0 {
if err := batch.Write(); err != nil {
return err
}
db.abort()
return nil
}
// Abort all changes to the underlying database
func (db *Database) Abort() {
db.lock.Lock()
defer db.lock.Unlock()
db.abort()
}
func (db *Database) abort() { db.mem = make(map[string]valueDelete, memdb.DefaultSize) }
// CommitBatch returns a batch that will commit all pending writes to the underlying database
func (db *Database) CommitBatch() (database.Batch, error) {
db.lock.Lock()
defer db.lock.Unlock()
return db.commitBatch()
}
func (db *Database) commitBatch() (database.Batch, error) {
if db.mem == nil {
return nil, database.ErrClosed
}
batch := db.db.NewBatch()
for key, value := range db.mem {
if value.delete {
if err := batch.Delete([]byte(key)); err != nil {
return err
return nil, err
}
} else if err := batch.Put([]byte(key), value.value); err != nil {
return err
return nil, err
}
}
if err := batch.Write(); err != nil {
return err
return nil, err
}
db.mem = make(map[string]valueDelete, memdb.DefaultSize)
return nil
return batch, nil
}
// Close implements the database.Database interface
@ -289,6 +315,9 @@ func (b *batch) Replay(w database.KeyValueWriter) error {
return nil
}
// Inner returns itself
func (b *batch) Inner() database.Batch { return b }
// iterator walks over both the in memory database and the underlying database
// at the same time.
type iterator struct {

View File

@ -256,6 +256,72 @@ func TestCommitClosedDelete(t *testing.T) {
}
}
func TestAbort(t *testing.T) {
baseDB := memdb.New()
db := New(baseDB)
key1 := []byte("hello1")
value1 := []byte("world1")
if err := db.Put(key1, value1); err != nil {
t.Fatalf("Unexpected error on db.Put: %s", err)
}
if value, err := db.Get(key1); err != nil {
t.Fatalf("Unexpected error on db.Get: %s", err)
} else if !bytes.Equal(value, value1) {
t.Fatalf("db.Get Returned: 0x%x ; Expected: 0x%x", value, value1)
} else if has, err := baseDB.Has(key1); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if has {
t.Fatalf("db.Has Returned: %v ; Expected: %v", has, false)
}
db.Abort()
if has, err := db.Has(key1); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if has {
t.Fatalf("db.Has Returned: %v ; Expected: %v", has, false)
} else if has, err := baseDB.Has(key1); err != nil {
t.Fatalf("Unexpected error on db.Has: %s", err)
} else if has {
t.Fatalf("db.Has Returned: %v ; Expected: %v", has, false)
}
}
func TestCommitBatch(t *testing.T) {
baseDB := memdb.New()
db := New(baseDB)
key1 := []byte("hello1")
value1 := []byte("world1")
if err := db.Put(key1, value1); err != nil {
t.Fatalf("Unexpected error on db.Put: %s", err)
}
batch, err := db.CommitBatch()
if err != nil {
t.Fatalf("Unexpected error on db.CommitBatch: %s", err)
}
db.Abort()
if err := batch.Write(); err != nil {
t.Fatalf("Unexpected error on batch.Write: %s", err)
}
if value, err := db.Get(key1); err != nil {
t.Fatalf("Unexpected error on db.Get: %s", err)
} else if !bytes.Equal(value, value1) {
t.Fatalf("db.Get Returned: 0x%x ; Expected: 0x%x", value, value1)
} else if value, err := baseDB.Get(key1); err != nil {
t.Fatalf("Unexpected error on db.Get: %s", err)
} else if !bytes.Equal(value, value1) {
t.Fatalf("db.Get Returned: 0x%x ; Expected: 0x%x", value, value1)
}
}
func TestSetDatabase(t *testing.T) {
baseDB := memdb.New()
newDB := memdb.New()

View File

@ -14,8 +14,10 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/evm"
"github.com/ava-labs/gecko/vms/platformvm"
"github.com/ava-labs/gecko/vms/secp256k1fx"
"github.com/ava-labs/gecko/vms/spchainvm"
"github.com/ava-labs/gecko/vms/spdagvm"
"github.com/ava-labs/gecko/vms/timestampvm"
@ -192,7 +194,7 @@ func Genesis(networkID uint32) []byte {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x05, 0xde, 0x31, 0xb4, 0xd8, 0xb2, 0x29, 0x91,
0x0b, 0xde, 0x31, 0xb4, 0xd8, 0xb2, 0x29, 0x91,
0xd5, 0x1a, 0xa6, 0xaa, 0x1f, 0xc7, 0x33, 0xf2,
0x3a, 0x85, 0x1a, 0x8c, 0x94, 0x00, 0x00, 0x12,
0x30, 0x9c, 0xe5, 0x40, 0x00, 0x00, 0x00, 0x00,
@ -210,7 +212,7 @@ func Genesis(networkID uint32) []byte {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xaa, 0x18,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xaa, 0x18,
0xd3, 0x99, 0x1c, 0xf6, 0x37, 0xaa, 0x6c, 0x16,
0x2f, 0x5e, 0x95, 0xcf, 0x16, 0x3f, 0x69, 0xcd,
0x82, 0x91, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5,
@ -229,7 +231,7 @@ func Genesis(networkID uint32) []byte {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x05, 0xe9, 0x09, 0x4f, 0x73, 0x69,
0x00, 0x00, 0x0b, 0xe9, 0x09, 0x4f, 0x73, 0x69,
0x80, 0x02, 0xfd, 0x52, 0xc9, 0x08, 0x19, 0xb4,
0x57, 0xb9, 0xfb, 0xc8, 0x66, 0xab, 0x80, 0x00,
0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00, 0x00,
@ -247,7 +249,7 @@ func Genesis(networkID uint32) []byte {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b,
0x47, 0x9f, 0x66, 0xc8, 0xbe, 0x89, 0x58, 0x30,
0x54, 0x7e, 0x70, 0xb4, 0xb2, 0x98, 0xca, 0xfd,
0x43, 0x3d, 0xba, 0x6e, 0x00, 0x00, 0x12, 0x30,
@ -266,7 +268,7 @@ func Genesis(networkID uint32) []byte {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0xf2, 0x9b, 0xce,
0x00, 0x00, 0x00, 0x00, 0x0b, 0xf2, 0x9b, 0xce,
0x5f, 0x34, 0xa7, 0x43, 0x01, 0xeb, 0x0d, 0xe7,
0x16, 0xd5, 0x19, 0x4e, 0x4a, 0x4a, 0xea, 0x5d,
0x7a, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40,
@ -306,7 +308,7 @@ func Genesis(networkID uint32) []byte {
0x00, 0x03, 0x41, 0x56, 0x41, 0x00, 0x03, 0x41,
0x56, 0x41, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x00, 0x9f, 0xdf, 0x42, 0xf6,
0x00, 0x00, 0x06, 0x00, 0x9f, 0xdf, 0x42, 0xf6,
0xe4, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x01, 0x3c, 0xb7, 0xd3, 0x84, 0x2e,
@ -514,3 +516,29 @@ func VMGenesis(networkID uint32, vmID ids.ID) *platformvm.CreateChainTx {
}
return nil
}
// AVAAssetID ...
func AVAAssetID(networkID uint32) ids.ID {
createAVM := VMGenesis(networkID, avm.ID)
c := codec.NewDefault()
c.RegisterType(&avm.BaseTx{})
c.RegisterType(&avm.CreateAssetTx{})
c.RegisterType(&avm.OperationTx{})
c.RegisterType(&avm.ImportTx{})
c.RegisterType(&avm.ExportTx{})
c.RegisterType(&secp256k1fx.MintOutput{})
c.RegisterType(&secp256k1fx.TransferOutput{})
c.RegisterType(&secp256k1fx.MintInput{})
c.RegisterType(&secp256k1fx.TransferInput{})
c.RegisterType(&secp256k1fx.Credential{})
genesis := avm.Genesis{}
c.Unmarshal(createAVM.GenesisData, &genesis)
genesisTx := genesis.Txs[0]
tx := avm.Tx{UnsignedTx: &genesisTx.CreateAssetTx}
txBytes, _ := c.Marshal(&tx)
tx.Initialize(txBytes)
return tx.ID()
}

View File

@ -25,6 +25,7 @@ import (
"github.com/ava-labs/gecko/api/keystore"
"github.com/ava-labs/gecko/api/metrics"
"github.com/ava-labs/gecko/chains"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/prefixdb"
"github.com/ava-labs/gecko/genesis"
@ -68,6 +69,9 @@ type Node struct {
// Handles calls to Keystore API
keystoreServer keystore.Keystore
// Manages shared memory
sharedMemory atomic.SharedMemory
// Manages creation of blockchains and routing messages to them
chainManager chains.Manager
@ -322,7 +326,10 @@ func (n *Node) initNodeID() error {
// its factory needs to reference n.chainManager, which is nil right now
func (n *Node) initVMManager() {
n.vmManager = vms.NewManager(&n.APIServer, n.HTTPLog)
n.vmManager.RegisterVMFactory(avm.ID, &avm.Factory{})
n.vmManager.RegisterVMFactory(avm.ID, &avm.Factory{
AVA: genesis.AVAAssetID(n.Config.NetworkID),
Platform: ids.Empty,
})
n.vmManager.RegisterVMFactory(evm.ID, &evm.Factory{})
n.vmManager.RegisterVMFactory(spdagvm.ID, &spdagvm.Factory{TxFee: n.Config.AvaTxFee})
n.vmManager.RegisterVMFactory(spchainvm.ID, &spchainvm.Factory{})
@ -358,6 +365,8 @@ func (n *Node) initChains() {
/*vmFactory=*/ &platformvm.Factory{
ChainManager: n.chainManager,
Validators: vdrs,
AVA: genesis.AVAAssetID(n.Config.NetworkID),
AVM: genesis.VMGenesis(n.Config.NetworkID, avm.ID).ID(),
},
)
@ -415,12 +424,20 @@ func (n *Node) initChainManager() {
n.ValidatorAPI,
&n.APIServer,
&n.keystoreServer,
&n.sharedMemory,
)
n.chainManager.AddRegistrant(&n.APIServer)
}
// initWallet initializes the Wallet service
// initSharedMemory initializes the shared memory for cross chain interation
func (n *Node) initSharedMemory() {
n.Log.Info("initializing SharedMemory")
sharedMemoryDB := prefixdb.New([]byte("shared memory"), n.DB)
n.sharedMemory.Initialize(n.Log, sharedMemoryDB)
}
// initKeystoreAPI initializes the keystore service
// Assumes n.APIServer is already set
func (n *Node) initKeystoreAPI() {
n.Log.Info("initializing Keystore API")
@ -502,6 +519,9 @@ func (n *Node) Initialize(Config *Config, logger logging.Logger, logFactory logg
return fmt.Errorf("problem initializing staker ID: %w", err)
}
// initialize shared memory
n.initSharedMemory()
// Start HTTP APIs
n.initAPIServer() // Start the API Server
n.initKeystoreAPI() // Start the Keystore API

View File

@ -14,6 +14,7 @@ type TestTx struct {
Deps []Tx
Ins ids.Set
Stat choices.Status
Validity error
Bits []byte
}
@ -39,7 +40,7 @@ func (tx *TestTx) Reject() { tx.Stat = choices.Rejected }
func (tx *TestTx) Reset() { tx.Stat = choices.Processing }
// Verify returns nil
func (tx *TestTx) Verify() error { return nil }
func (tx *TestTx) Verify() error { return tx.Validity }
// Bytes returns the bits
func (tx *TestTx) Bytes() []byte { return tx.Bits }

View File

@ -24,6 +24,12 @@ type Keystore interface {
GetDatabase(username, password string) (database.Database, error)
}
// SharedMemory ...
type SharedMemory interface {
GetDatabase(id ids.ID) database.Database
ReleaseDatabase(id ids.ID)
}
// AliasLookup ...
type AliasLookup interface {
Lookup(alias string) (ids.ID, error)
@ -44,6 +50,7 @@ type Context struct {
Lock sync.RWMutex
HTTP Callable
Keystore Keystore
SharedMemory SharedMemory
BCLookup AliasLookup
}

View File

@ -6,6 +6,7 @@ package avalanche
import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/consensus/avalanche"
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
)
type issuer struct {
@ -44,13 +45,23 @@ func (i *issuer) Update() {
vtxID := i.vtx.ID()
i.t.pending.Remove(vtxID)
for _, tx := range i.vtx.Txs() {
txs := i.vtx.Txs()
validTxs := []snowstorm.Tx{}
for _, tx := range txs {
if err := tx.Verify(); err != nil {
i.t.Config.Context.Log.Debug("Transaction failed verification due to %s, dropping vertex", err)
i.t.Config.Context.Log.Debug("Transaction %s failed verification due to %s", tx.ID(), err)
} else {
validTxs = append(validTxs, tx)
}
}
if len(validTxs) != len(txs) {
i.t.Config.Context.Log.Debug("Abandoning %s due to failed transaction verification", vtxID)
i.t.batch(validTxs, false /*=force*/, false /*=empty*/)
i.t.vtxBlocked.Abandon(vtxID)
return
}
}
i.t.Config.Context.Log.Verbo("Adding vertex to consensus:\n%s", i.vtx)

View File

@ -309,7 +309,7 @@ func (t *Transitive) batch(txs []snowstorm.Tx, force, empty bool) {
}
// Force allows for a conflict to be issued
if txID := tx.ID(); !overlaps && !issuedTxs.Contains(txID) && (force || (t.Consensus.IsVirtuous(tx))) && !tx.Status().Decided() {
if txID := tx.ID(); !overlaps && !issuedTxs.Contains(txID) && (force || t.Consensus.IsVirtuous(tx)) && !tx.Status().Decided() {
batch = append(batch, tx)
issuedTxs.Add(txID)
consumed.Union(inputs)

View File

@ -315,6 +315,18 @@ func TestEngineQuery(t *testing.T) {
if !bytes.Equal(b, vtx1.Bytes()) {
t.Fatalf("Wrong bytes")
}
st.getVertex = func(vtxID ids.ID) (avalanche.Vertex, error) {
if vtxID.Equals(vtx0.ID()) {
return &Vtx{status: choices.Processing}, nil
}
if vtxID.Equals(vtx1.ID()) {
return vtx1, nil
}
t.Fatalf("Wrong vertex requested")
panic("Should have failed")
}
return vtx1, nil
}
te.Put(vdr.ID(), 0, vtx1.ID(), vtx1.Bytes())
@ -2363,3 +2375,175 @@ func TestEngineBootstrappingIntoConsensus(t *testing.T) {
sender.PushQueryF = nil
st.getVertex = nil
}
func TestEngineUndeclaredDependencyDeadlock(t *testing.T) {
config := DefaultConfig()
vdr := validators.GenerateRandomValidator(1)
vals := validators.NewSet()
config.Validators = vals
vals.Add(vdr)
st := &stateTest{t: t}
config.State = st
gVtx := &Vtx{
id: GenerateID(),
status: choices.Accepted,
}
vts := []avalanche.Vertex{gVtx}
utxos := []ids.ID{GenerateID(), GenerateID()}
tx0 := &TestTx{
TestTx: snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
},
}
tx0.Ins.Add(utxos[0])
tx1 := &TestTx{
TestTx: snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
Validity: errors.New(""),
},
}
tx1.Ins.Add(utxos[1])
vtx0 := &Vtx{
parents: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0},
height: 1,
status: choices.Processing,
}
vtx1 := &Vtx{
parents: []avalanche.Vertex{vtx0},
id: GenerateID(),
txs: []snowstorm.Tx{tx1},
height: 2,
status: choices.Processing,
}
te := &Transitive{}
te.Initialize(config)
te.finishBootstrapping()
sender := &common.SenderTest{}
sender.T = t
te.Config.Sender = sender
reqID := new(uint32)
sender.PushQueryF = func(_ ids.ShortSet, requestID uint32, _ ids.ID, _ []byte) {
*reqID = requestID
}
te.insert(vtx0)
sender.PushQueryF = func(ids.ShortSet, uint32, ids.ID, []byte) {
t.Fatalf("should have failed verification")
}
te.insert(vtx1)
st.getVertex = func(vtxID ids.ID) (avalanche.Vertex, error) {
switch {
case vtxID.Equals(vtx0.ID()):
return vtx0, nil
case vtxID.Equals(vtx1.ID()):
return vtx1, nil
}
return nil, errors.New("Unknown vtx")
}
votes := ids.Set{}
votes.Add(vtx1.ID())
te.Chits(vdr.ID(), *reqID, votes)
if status := vtx0.Status(); status != choices.Accepted {
t.Fatalf("should have accepted the vertex due to transitive voting")
}
}
func TestEnginePartiallyValidVertex(t *testing.T) {
config := DefaultConfig()
vdr := validators.GenerateRandomValidator(1)
vals := validators.NewSet()
config.Validators = vals
vals.Add(vdr)
st := &stateTest{t: t}
config.State = st
gVtx := &Vtx{
id: GenerateID(),
status: choices.Accepted,
}
vts := []avalanche.Vertex{gVtx}
utxos := []ids.ID{GenerateID(), GenerateID()}
tx0 := &TestTx{
TestTx: snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
},
}
tx0.Ins.Add(utxos[0])
tx1 := &TestTx{
TestTx: snowstorm.TestTx{
Identifier: GenerateID(),
Stat: choices.Processing,
Validity: errors.New(""),
},
}
tx1.Ins.Add(utxos[1])
vtx := &Vtx{
parents: vts,
id: GenerateID(),
txs: []snowstorm.Tx{tx0, tx1},
height: 1,
status: choices.Processing,
}
te := &Transitive{}
te.Initialize(config)
te.finishBootstrapping()
expectedVtxID := GenerateID()
st.buildVertex = func(_ ids.Set, txs []snowstorm.Tx) (avalanche.Vertex, error) {
consumers := []snowstorm.Tx{}
for _, tx := range txs {
consumers = append(consumers, tx)
}
return &Vtx{
parents: vts,
id: expectedVtxID,
txs: consumers,
status: choices.Processing,
bytes: []byte{1},
}, nil
}
sender := &common.SenderTest{}
sender.T = t
te.Config.Sender = sender
sender.PushQueryF = func(_ ids.ShortSet, _ uint32, vtxID ids.ID, _ []byte) {
if !expectedVtxID.Equals(vtxID) {
t.Fatalf("wrong vertex queried")
}
}
te.insert(vtx)
}

View File

@ -54,12 +54,9 @@ func (t *txJob) Execute() {
case choices.Unknown, choices.Rejected:
t.numDropped.Inc()
case choices.Processing:
if err := t.tx.Verify(); err == nil {
t.tx.Verify()
t.tx.Accept()
t.numAccepted.Inc()
} else {
t.numDropped.Inc()
}
}
}
func (t *txJob) Bytes() []byte { return t.tx.Bytes() }

View File

@ -5,6 +5,7 @@ package avalanche
import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/consensus/avalanche"
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
)
@ -34,6 +35,7 @@ func (v *voter) Update() {
if !finished {
return
}
results = v.bubbleVotes(results)
v.t.Config.Context.Log.Debug("Finishing poll with:\n%s", &results)
v.t.Consensus.RecordPoll(results)
@ -62,3 +64,29 @@ func (v *voter) Update() {
v.t.repoll()
}
}
func (v *voter) bubbleVotes(votes ids.UniqueBag) ids.UniqueBag {
bubbledVotes := ids.UniqueBag{}
for _, vote := range votes.List() {
set := votes.GetSet(vote)
vtx, err := v.t.Config.State.GetVertex(vote)
if err != nil {
continue
}
vts := []avalanche.Vertex{vtx}
for len(vts) > 0 {
vtx := vts[0]
vts = vts[1:]
if status := vtx.Status(); status.Fetched() && !v.t.Consensus.VertexIssued(vtx) {
vts = append(vts, vtx.Parents()...)
} else if !status.Decided() && v.t.Consensus.VertexIssued(vtx) {
bubbledVotes.UnionSet(vtx.ID(), set)
} else {
v.t.Config.Context.Log.Debug("Dropping %d vote(s) for %s because the vertex is invalid", set.Len(), vtx.ID())
}
}
}
return bubbledVotes
}

View File

@ -51,12 +51,9 @@ func (b *blockJob) Execute() {
case choices.Unknown, choices.Rejected:
b.numDropped.Inc()
case choices.Processing:
if err := b.blk.Verify(); err == nil {
b.blk.Verify()
b.blk.Accept()
b.numAccepted.Inc()
} else {
b.numDropped.Inc()
}
}
}
func (b *blockJob) Bytes() []byte { return b.blk.Bytes() }

View File

@ -27,6 +27,7 @@ type Blk struct {
height int
status choices.Status
validity error
bytes []byte
}
@ -36,7 +37,7 @@ func (b *Blk) Parent() snowman.Block { return b.parent }
func (b *Blk) Accept() { b.status = choices.Accepted }
func (b *Blk) Reject() { b.status = choices.Rejected }
func (b *Blk) Status() choices.Status { return b.status }
func (b *Blk) Verify() error { return nil }
func (b *Blk) Verify() error { return b.validity }
func (b *Blk) Bytes() []byte { return b.bytes }
type sortBks []*Blk

View File

@ -280,6 +280,18 @@ func TestEngineQuery(t *testing.T) {
if !bytes.Equal(b, blk1.Bytes()) {
t.Fatalf("Wrong bytes")
}
vm.GetBlockF = func(blkID ids.ID) (snowman.Block, error) {
switch {
case blkID.Equals(blk.ID()):
return blk, nil
case blkID.Equals(blk1.ID()):
return blk1, nil
}
t.Fatalf("Wrong block requested")
panic("Should have failed")
}
return blk1, nil
}
te.Put(vdr.ID(), *getRequestID, blk1.ID(), blk1.Bytes())
@ -418,6 +430,17 @@ func TestEngineMultipleQuery(t *testing.T) {
te.Chits(vdr1.ID(), *queryRequestID, blkSet)
vm.ParseBlockF = func(b []byte) (snowman.Block, error) {
vm.GetBlockF = func(blkID ids.ID) (snowman.Block, error) {
switch {
case blkID.Equals(blk0.ID()):
return blk0, nil
case blkID.Equals(blk1.ID()):
return blk1, nil
}
t.Fatalf("Wrong block requested")
panic("Should have failed")
}
return blk1, nil
}
@ -1076,3 +1099,60 @@ func TestEngineRetryFetch(t *testing.T) {
t.Fatalf("Should have requested the block again")
}
}
func TestEngineUndeclaredDependencyDeadlock(t *testing.T) {
vdr, _, sender, vm, te, gBlk := setup(t)
sender.Default(true)
validBlk := &Blk{
parent: gBlk,
id: GenerateID(),
height: 1,
status: choices.Processing,
bytes: []byte{1},
}
invalidBlk := &Blk{
parent: validBlk,
id: GenerateID(),
height: 2,
status: choices.Processing,
validity: errors.New("invalid due to an undeclared dependency"),
bytes: []byte{2},
}
validBlkID := validBlk.ID()
invalidBlkID := invalidBlk.ID()
reqID := new(uint32)
sender.PushQueryF = func(_ ids.ShortSet, requestID uint32, _ ids.ID, _ []byte) {
*reqID = requestID
}
te.insert(validBlk)
sender.PushQueryF = nil
te.insert(invalidBlk)
vm.GetBlockF = func(blkID ids.ID) (snowman.Block, error) {
switch {
case blkID.Equals(validBlkID):
return validBlk, nil
case blkID.Equals(invalidBlkID):
return invalidBlk, nil
}
return nil, errUnknownBlock
}
votes := ids.Set{}
votes.Add(invalidBlkID)
te.Chits(vdr.ID(), *reqID, votes)
vm.GetBlockF = nil
if status := validBlk.Status(); status != choices.Accepted {
t.Fatalf("Should have bubbled invalid votes to the valid parent")
}
}

View File

@ -41,6 +41,10 @@ func (v *voter) Update() {
return
}
// To prevent any potential deadlocks with un-disclosed dependencies, votes
// must be bubbled to the nearest valid block
results = v.bubbleVotes(results)
v.t.Config.Context.Log.Verbo("Finishing poll [%d] with:\n%s", v.requestID, &results)
v.t.Consensus.RecordPoll(results)
@ -57,3 +61,23 @@ func (v *voter) Update() {
v.t.repoll()
}
}
func (v *voter) bubbleVotes(votes ids.Bag) ids.Bag {
bubbledVotes := ids.Bag{}
for _, vote := range votes.List() {
count := votes.Count(vote)
blk, err := v.t.Config.VM.GetBlock(vote)
if err != nil {
continue
}
for blk.Status().Fetched() && !v.t.Consensus.Issued(blk) {
blk = blk.Parent()
}
if !blk.Status().Decided() && v.t.Consensus.Issued(blk) {
bubbledVotes.AddCount(blk.ID(), count)
}
}
return bubbledVotes
}

View File

@ -21,6 +21,9 @@ const (
// SECP256K1SKLen is the number of bytes in a secp2561k private key
SECP256K1SKLen = 32
// SECP256K1PKLen is the number of bytes in a secp2561k public key
SECP256K1PKLen = 33
)
// FactorySECP256K1 ...

View File

@ -27,6 +27,10 @@ const (
// SECP256K1RSKLen is the number of bytes in a secp2561k recoverable private
// key
SECP256K1RSKLen = 32
// SECP256K1RPKLen is the number of bytes in a secp2561k recoverable public
// key
SECP256K1RPKLen = 33
)
// FactorySECP256K1R ...

View File

@ -6,10 +6,12 @@ package avm
import (
"errors"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/utils/math"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
)
var (
@ -27,31 +29,17 @@ var (
// BaseTx is the basis of all transactions.
type BaseTx struct {
metadata
ava.Metadata
NetID uint32 `serialize:"true"` // ID of the network this chain lives on
BCID ids.ID `serialize:"true"` // ID of the chain on which this transaction exists (prevents replay attacks)
Outs []*TransferableOutput `serialize:"true"` // The outputs of this transaction
Ins []*TransferableInput `serialize:"true"` // The inputs to this transaction
Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction
Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction
}
// NetworkID is the ID of the network on which this transaction exists
func (t *BaseTx) NetworkID() uint32 { return t.NetID }
// ChainID is the ID of the chain on which this transaction exists
func (t *BaseTx) ChainID() ids.ID { return t.BCID }
// Outputs track which outputs this transaction is producing. The returned array
// should not be modified.
func (t *BaseTx) Outputs() []*TransferableOutput { return t.Outs }
// Inputs track which UTXOs this transaction is consuming. The returned array
// should not be modified.
func (t *BaseTx) Inputs() []*TransferableInput { return t.Ins }
// InputUTXOs track which UTXOs this transaction is consuming.
func (t *BaseTx) InputUTXOs() []*UTXOID {
utxos := []*UTXOID(nil)
func (t *BaseTx) InputUTXOs() []*ava.UTXOID {
utxos := []*ava.UTXOID(nil)
for _, in := range t.Ins {
utxos = append(utxos, &in.UTXOID)
}
@ -68,18 +56,16 @@ func (t *BaseTx) AssetIDs() ids.Set {
}
// UTXOs returns the UTXOs transaction is producing.
func (t *BaseTx) UTXOs() []*UTXO {
func (t *BaseTx) UTXOs() []*ava.UTXO {
txID := t.ID()
utxos := make([]*UTXO, len(t.Outs))
utxos := make([]*ava.UTXO, len(t.Outs))
for i, out := range t.Outs {
utxos[i] = &UTXO{
UTXOID: UTXOID{
utxos[i] = &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: txID,
OutputIndex: uint32(i),
},
Asset: Asset{
ID: out.AssetID(),
},
Asset: ava.Asset{ID: out.AssetID()},
Out: out.Out,
}
}
@ -97,12 +83,14 @@ func (t *BaseTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error
return errWrongChainID
}
fc := ava.NewFlowChecker()
for _, out := range t.Outs {
if err := out.Verify(); err != nil {
return err
}
fc.Produce(out.AssetID(), out.Output().Amount())
}
if !IsSortedTransferableOutputs(t.Outs, c) {
if !ava.IsSortedTransferableOutputs(t.Outs, c) {
return errOutputsNotSorted
}
@ -110,101 +98,37 @@ func (t *BaseTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error
if err := in.Verify(); err != nil {
return err
}
fc.Consume(in.AssetID(), in.Input().Amount())
}
if !isSortedAndUniqueTransferableInputs(t.Ins) {
if !ava.IsSortedAndUniqueTransferableInputs(t.Ins) {
return errInputsNotSortedUnique
}
consumedFunds := map[[32]byte]uint64{}
for _, in := range t.Ins {
assetID := in.AssetID()
amount := in.Input().Amount()
// TODO: Add the Tx fee to the produced side
var err error
assetIDKey := assetID.Key()
consumedFunds[assetIDKey], err = math.Add64(consumedFunds[assetIDKey], amount)
if err != nil {
return errInputOverflow
}
}
producedFunds := map[[32]byte]uint64{}
for _, out := range t.Outs {
assetID := out.AssetID()
amount := out.Output().Amount()
var err error
assetIDKey := assetID.Key()
producedFunds[assetIDKey], err = math.Add64(producedFunds[assetIDKey], amount)
if err != nil {
return errOutputOverflow
}
if err := fc.Verify(); err != nil {
return err
}
// TODO: Add the Tx fee to the producedFunds
for assetID, producedAssetAmount := range producedFunds {
consumedAssetAmount := consumedFunds[assetID]
if producedAssetAmount > consumedAssetAmount {
return errInsufficientFunds
}
}
return t.metadata.Verify()
return t.Metadata.Verify()
}
// SemanticVerify that this transaction is valid to be spent.
func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) error {
func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error {
for i, in := range t.Ins {
cred := creds[i]
fxIndex, err := vm.getFx(cred.Cred)
fxIndex, err := vm.getFx(cred)
if err != nil {
return err
}
fx := vm.fxs[fxIndex].Fx
utxoID := in.InputID()
utxo, err := vm.state.UTXO(utxoID)
if err == nil {
utxoAssetID := utxo.AssetID()
inAssetID := in.AssetID()
if !utxoAssetID.Equals(inAssetID) {
return errAssetIDMismatch
}
if !vm.verifyFxUsage(fxIndex, inAssetID) {
return errIncompatibleFx
}
err = fx.VerifyTransfer(uTx, utxo.Out, in.In, cred.Cred)
if err == nil {
continue
}
utxo, err := vm.getUTXO(&in.UTXOID)
if err != nil {
return err
}
inputTx, inputIndex := in.InputSource()
parent := UniqueTx{
vm: vm,
txID: inputTx,
}
if err := parent.Verify(); err != nil {
return errMissingUTXO
} else if status := parent.Status(); status.Decided() {
return errMissingUTXO
}
utxos := parent.UTXOs()
if uint32(len(utxos)) <= inputIndex || int(inputIndex) < 0 {
return errInvalidUTXO
}
utxo = utxos[int(inputIndex)]
utxoAssetID := utxo.AssetID()
inAssetID := in.AssetID()
if !utxoAssetID.Equals(inAssetID) {
@ -215,9 +139,12 @@ func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) erro
return errIncompatibleFx
}
if err := fx.VerifyTransfer(uTx, utxo.Out, in.In, cred.Cred); err != nil {
if err := fx.VerifyTransfer(uTx, utxo.Out, in.In, cred); err != nil {
return err
}
}
return nil
}
// ExecuteWithSideEffects writes the batch with any additional side effects
func (t *BaseTx) ExecuteWithSideEffects(_ *VM, batch database.Batch) error { return batch.Write() }

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ import (
"unicode"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
)
@ -43,18 +44,18 @@ type CreateAssetTx struct {
func (t *CreateAssetTx) InitialStates() []*InitialState { return t.States }
// UTXOs returns the UTXOs transaction is producing.
func (t *CreateAssetTx) UTXOs() []*UTXO {
func (t *CreateAssetTx) UTXOs() []*ava.UTXO {
txID := t.ID()
utxos := t.BaseTx.UTXOs()
for _, state := range t.States {
for _, out := range state.Outs {
utxos = append(utxos, &UTXO{
UTXOID: UTXOID{
utxos = append(utxos, &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: txID,
OutputIndex: uint32(len(utxos)),
},
Asset: Asset{
Asset: ava.Asset{
ID: txID,
},
Out: out,
@ -110,10 +111,5 @@ func (t *CreateAssetTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs
return nil
}
// SemanticVerify that this transaction is well-formed.
func (t *CreateAssetTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) error {
return t.BaseTx.SemanticVerify(vm, uTx, creds)
}
// Sort ...
func (t *CreateAssetTx) Sort() { sortInitialStates(t.States) }

View File

@ -8,6 +8,7 @@ import (
"testing"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
"github.com/ava-labs/gecko/vms/secp256k1fx"
@ -92,9 +93,8 @@ func TestCreateAssetTxSerialization(t *testing.T) {
0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa,
0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88,
}),
Outs: []*TransferableOutput{
&TransferableOutput{
Asset: Asset{
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{
ID: ids.NewID([32]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
@ -121,11 +121,9 @@ func TestCreateAssetTxSerialization(t *testing.T) {
},
},
},
},
},
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
}},
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: ids.NewID([32]byte{
0xf1, 0xe1, 0xd1, 0xc1, 0xb1, 0xa1, 0x91, 0x81,
0x71, 0x61, 0x51, 0x41, 0x31, 0x21, 0x11, 0x01,
@ -134,7 +132,7 @@ func TestCreateAssetTxSerialization(t *testing.T) {
}),
OutputIndex: 5,
},
Asset: Asset{
Asset: ava.Asset{
ID: ids.NewID([32]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
@ -148,8 +146,7 @@ func TestCreateAssetTxSerialization(t *testing.T) {
SigIndices: []uint32{3, 7},
},
},
},
},
}},
},
Name: "Volatility Index",
Symbol: "VIX",

View File

@ -1,36 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
import (
"errors"
"github.com/ava-labs/gecko/vms/components/verify"
)
var (
errNilCredential = errors.New("nil credential is not valid")
errNilFxCredential = errors.New("nil feature extension credential is not valid")
)
// Credential ...
type Credential struct {
Cred verify.Verifiable `serialize:"true"`
}
// Credential returns the feature extension credential that this Credential is
// using.
func (cred *Credential) Credential() verify.Verifiable { return cred.Cred }
// Verify implements the verify.Verifiable interface
func (cred *Credential) Verify() error {
switch {
case cred == nil:
return errNilCredential
case cred.Cred == nil:
return errNilFxCredential
default:
return cred.Cred.Verify()
}
}

View File

@ -1,36 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
import (
"testing"
)
func TestCredentialVerifyNil(t *testing.T) {
cred := (*Credential)(nil)
if err := cred.Verify(); err == nil {
t.Fatalf("Should have errored due to nil credential")
}
}
func TestCredentialVerifyNilFx(t *testing.T) {
cred := &Credential{}
if err := cred.Verify(); err == nil {
t.Fatalf("Should have errored due to nil fx credential")
}
}
func TestCredential(t *testing.T) {
cred := &Credential{
Cred: &testVerifiable{},
}
if err := cred.Verify(); err != nil {
t.Fatal(err)
}
if cred.Credential() != cred.Cred {
t.Fatalf("Should have returned the fx credential")
}
}

144
vms/avm/export_tx.go Normal file
View File

@ -0,0 +1,144 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
import (
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
)
// ExportTx is the basis of all transactions.
type ExportTx struct {
BaseTx `serialize:"true"`
Outs []*ava.TransferableOutput `serialize:"true"` // The outputs this transaction is sending to the other chain
}
// SyntacticVerify that this transaction is well-formed.
func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error {
switch {
case t == nil:
return errNilTx
case t.NetID != ctx.NetworkID:
return errWrongNetworkID
case !t.BCID.Equals(ctx.ChainID):
return errWrongChainID
}
fc := ava.NewFlowChecker()
for _, out := range t.BaseTx.Outs {
if err := out.Verify(); err != nil {
return err
}
fc.Produce(out.AssetID(), out.Output().Amount())
}
if !ava.IsSortedTransferableOutputs(t.BaseTx.Outs, c) {
return errOutputsNotSorted
}
for _, out := range t.Outs {
if err := out.Verify(); err != nil {
return err
}
fc.Produce(out.AssetID(), out.Output().Amount())
}
if !ava.IsSortedTransferableOutputs(t.Outs, c) {
return errOutputsNotSorted
}
for _, in := range t.Ins {
if err := in.Verify(); err != nil {
return err
}
fc.Consume(in.AssetID(), in.Input().Amount())
}
if !ava.IsSortedAndUniqueTransferableInputs(t.Ins) {
return errInputsNotSortedUnique
}
// TODO: Add the Tx fee to the produced side
if err := fc.Verify(); err != nil {
return err
}
return t.Metadata.Verify()
}
// SemanticVerify that this transaction is valid to be spent.
func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error {
for i, in := range t.Ins {
cred := creds[i]
fxIndex, err := vm.getFx(cred)
if err != nil {
return err
}
fx := vm.fxs[fxIndex].Fx
utxo, err := vm.getUTXO(&in.UTXOID)
if err != nil {
return err
}
utxoAssetID := utxo.AssetID()
inAssetID := in.AssetID()
if !utxoAssetID.Equals(inAssetID) {
return errAssetIDMismatch
}
if !vm.verifyFxUsage(fxIndex, inAssetID) {
return errIncompatibleFx
}
if err := fx.VerifyTransfer(uTx, utxo.Out, in.In, cred); err != nil {
return err
}
}
for _, out := range t.Outs {
if !out.AssetID().Equals(vm.ava) {
return errWrongAssetID
}
}
return nil
}
// ExecuteWithSideEffects writes the batch with any additional side effects
func (t *ExportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error {
txID := t.ID()
smDB := vm.ctx.SharedMemory.GetDatabase(vm.platform)
defer vm.ctx.SharedMemory.ReleaseDatabase(vm.platform)
vsmDB := versiondb.New(smDB)
state := ava.NewPrefixedState(vsmDB, vm.codec)
for i, out := range t.Outs {
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: txID,
OutputIndex: uint32(len(t.BaseTx.Outs) + i),
},
Asset: ava.Asset{ID: out.AssetID()},
Out: out.Out,
}
if err := state.FundAVMUTXO(utxo); err != nil {
return err
}
}
sharedBatch, err := vsmDB.CommitBatch()
if err != nil {
return err
}
return atomic.WriteAll(batch, sharedBatch)
}

389
vms/avm/export_tx_test.go Normal file
View File

@ -0,0 +1,389 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
import (
"bytes"
"testing"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
func TestExportTxSerialization(t *testing.T) {
expected := []byte{
// txID:
0x00, 0x00, 0x00, 0x04,
// networkID:
0x00, 0x00, 0x00, 0x02,
// blockchainID:
0xff, 0xff, 0xff, 0xff, 0xee, 0xee, 0xee, 0xee,
0xdd, 0xdd, 0xdd, 0xdd, 0xcc, 0xcc, 0xcc, 0xcc,
0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa,
0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88,
// number of outs:
0x00, 0x00, 0x00, 0x00,
// number of inputs:
0x00, 0x00, 0x00, 0x01,
// utxoID:
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
// output index
0x00, 0x00, 0x00, 0x00,
// assetID:
0x1f, 0x3f, 0x5f, 0x7f, 0x9e, 0xbe, 0xde, 0xfe,
0x1d, 0x3d, 0x5d, 0x7d, 0x9c, 0xbc, 0xdc, 0xfc,
0x1b, 0x3b, 0x5b, 0x7b, 0x9a, 0xba, 0xda, 0xfa,
0x19, 0x39, 0x59, 0x79, 0x98, 0xb8, 0xd8, 0xf8,
// input:
// input ID:
0x00, 0x00, 0x00, 0x08,
// amount:
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
// num sig indices:
0x00, 0x00, 0x00, 0x01,
// sig index[0]:
0x00, 0x00, 0x00, 0x00,
// number of exported outs:
0x00, 0x00, 0x00, 0x00,
}
tx := &Tx{UnsignedTx: &ExportTx{BaseTx: BaseTx{
NetID: 2,
BCID: ids.NewID([32]byte{
0xff, 0xff, 0xff, 0xff, 0xee, 0xee, 0xee, 0xee,
0xdd, 0xdd, 0xdd, 0xdd, 0xcc, 0xcc, 0xcc, 0xcc,
0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa,
0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88,
}),
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{TxID: ids.NewID([32]byte{
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
})},
Asset: ava.Asset{ID: ids.NewID([32]byte{
0x1f, 0x3f, 0x5f, 0x7f, 0x9e, 0xbe, 0xde, 0xfe,
0x1d, 0x3d, 0x5d, 0x7d, 0x9c, 0xbc, 0xdc, 0xfc,
0x1b, 0x3b, 0x5b, 0x7b, 0x9a, 0xba, 0xda, 0xfa,
0x19, 0x39, 0x59, 0x79, 0x98, 0xb8, 0xd8, 0xf8,
})},
In: &secp256k1fx.TransferInput{
Amt: 1000,
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
},
}},
}}}
c := codec.NewDefault()
c.RegisterType(&BaseTx{})
c.RegisterType(&CreateAssetTx{})
c.RegisterType(&OperationTx{})
c.RegisterType(&ImportTx{})
c.RegisterType(&ExportTx{})
c.RegisterType(&secp256k1fx.MintOutput{})
c.RegisterType(&secp256k1fx.TransferOutput{})
c.RegisterType(&secp256k1fx.MintInput{})
c.RegisterType(&secp256k1fx.TransferInput{})
c.RegisterType(&secp256k1fx.Credential{})
b, err := c.Marshal(&tx.UnsignedTx)
if err != nil {
t.Fatal(err)
}
tx.Initialize(b)
result := tx.Bytes()
if !bytes.Equal(expected, result) {
t.Fatalf("\nExpected: 0x%x\nResult: 0x%x", expected, result)
}
}
// Test issuing an import transaction.
func TestIssueExportTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t)
issuer := make(chan common.Message, 1)
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
ctx := snow.DefaultContextTest()
ctx.NetworkID = networkID
ctx.ChainID = chainID
ctx.SharedMemory = sm.NewBlockchainSharedMemory(chainID)
genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t)
avaID := genesisTx.ID()
platformID := ids.Empty.Prefix(0)
ctx.Lock.Lock()
vm := &VM{
ava: avaID,
platform: platformID,
}
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
key := keys[0]
tx := &Tx{UnsignedTx: &ExportTx{
BaseTx: BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: avaID,
OutputIndex: 1,
},
Asset: ava.Asset{ID: avaID},
In: &secp256k1fx.TransferInput{
Amt: 50000,
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
},
}},
},
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{ID: avaID},
Out: &secp256k1fx.TransferOutput{
Amt: 50000,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{key.PublicKey().Address()},
},
},
}},
}}
unsignedBytes, err := vm.codec.Marshal(&tx.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)
tx.Creds = append(tx.Creds, &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
})
b, err := vm.codec.Marshal(tx)
if err != nil {
t.Fatal(err)
}
tx.Initialize(b)
if _, err := vm.IssueTx(tx.Bytes(), nil); err != nil {
t.Fatal(err)
}
ctx.Lock.Unlock()
msg := <-issuer
if msg != common.PendingTxs {
t.Fatalf("Wrong message")
}
ctx.Lock.Lock()
defer ctx.Lock.Unlock()
txs := vm.PendingTxs()
if len(txs) != 1 {
t.Fatalf("Should have returned %d tx(s)", 1)
}
parsedTx := txs[0]
if err := parsedTx.Verify(); err != nil {
t.Fatal(err)
}
parsedTx.Accept()
smDB := vm.ctx.SharedMemory.GetDatabase(platformID)
defer vm.ctx.SharedMemory.ReleaseDatabase(platformID)
state := ava.NewPrefixedState(smDB, vm.codec)
utxo := ava.UTXOID{
TxID: tx.ID(),
OutputIndex: 0,
}
utxoID := utxo.InputID()
if _, err := state.AVMUTXO(utxoID); err != nil {
t.Fatal(err)
}
}
// Test force accepting an import transaction.
func TestClearForceAcceptedExportTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t)
issuer := make(chan common.Message, 1)
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
ctx := snow.DefaultContextTest()
ctx.NetworkID = networkID
ctx.ChainID = chainID
ctx.SharedMemory = sm.NewBlockchainSharedMemory(chainID)
genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t)
avaID := genesisTx.ID()
platformID := ids.Empty.Prefix(0)
ctx.Lock.Lock()
vm := &VM{
ava: avaID,
platform: platformID,
}
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
key := keys[0]
tx := &Tx{UnsignedTx: &ExportTx{
BaseTx: BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: avaID,
OutputIndex: 1,
},
Asset: ava.Asset{ID: avaID},
In: &secp256k1fx.TransferInput{
Amt: 50000,
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
},
}},
},
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{ID: avaID},
Out: &secp256k1fx.TransferOutput{
Amt: 50000,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{key.PublicKey().Address()},
},
},
}},
}}
unsignedBytes, err := vm.codec.Marshal(&tx.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)
tx.Creds = append(tx.Creds, &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
})
b, err := vm.codec.Marshal(tx)
if err != nil {
t.Fatal(err)
}
tx.Initialize(b)
if _, err := vm.IssueTx(tx.Bytes(), nil); err != nil {
t.Fatal(err)
}
ctx.Lock.Unlock()
msg := <-issuer
if msg != common.PendingTxs {
t.Fatalf("Wrong message")
}
ctx.Lock.Lock()
defer ctx.Lock.Unlock()
txs := vm.PendingTxs()
if len(txs) != 1 {
t.Fatalf("Should have returned %d tx(s)", 1)
}
parsedTx := txs[0]
if err := parsedTx.Verify(); err != nil {
t.Fatal(err)
}
smDB := vm.ctx.SharedMemory.GetDatabase(platformID)
state := ava.NewPrefixedState(smDB, vm.codec)
utxo := ava.UTXOID{
TxID: tx.ID(),
OutputIndex: 0,
}
utxoID := utxo.InputID()
if err := state.SpendAVMUTXO(utxoID); err != nil {
t.Fatal(err)
}
vm.ctx.SharedMemory.ReleaseDatabase(platformID)
parsedTx.Accept()
smDB = vm.ctx.SharedMemory.GetDatabase(platformID)
defer vm.ctx.SharedMemory.ReleaseDatabase(platformID)
state = ava.NewPrefixedState(smDB, vm.codec)
if _, err := state.AVMUTXO(utxoID); err == nil {
t.Fatalf("should have failed to read the utxo")
}
}

View File

@ -13,7 +13,15 @@ var (
)
// Factory ...
type Factory struct{}
type Factory struct {
AVA ids.ID
Platform ids.ID
}
// New ...
func (f *Factory) New() interface{} { return &VM{} }
func (f *Factory) New() interface{} {
return &VM{
ava: f.AVA,
platform: f.Platform,
}
}

View File

@ -5,7 +5,6 @@ package avm
import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/components/verify"
)
type parsedFx struct {
@ -31,19 +30,3 @@ type Fx interface {
// credential, a non-nil error should be returned.
VerifyOperation(tx interface{}, utxos, ins, creds, outs []interface{}) error
}
// FxTransferable is the interface a feature extension must provide to transfer
// value between features extensions.
type FxTransferable interface {
verify.Verifiable
// Amount returns how much value this output consumes of the asset in its
// transaction.
Amount() uint64
}
// FxAddressable is the interface a feature extension must provide to be able to
// be tracked as a part of the utxo set for a set of addresses
type FxAddressable interface {
Addresses() [][]byte
}

165
vms/avm/import_tx.go Normal file
View File

@ -0,0 +1,165 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
import (
"errors"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
)
// ImportTx is a transaction that imports an asset from another blockchain.
type ImportTx struct {
BaseTx `serialize:"true"`
Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction
}
// InputUTXOs track which UTXOs this transaction is consuming.
func (t *ImportTx) InputUTXOs() []*ava.UTXOID {
utxos := t.BaseTx.InputUTXOs()
for _, in := range t.Ins {
in.Symbol = true
utxos = append(utxos, &in.UTXOID)
}
return utxos
}
// AssetIDs returns the IDs of the assets this transaction depends on
func (t *ImportTx) AssetIDs() ids.Set {
assets := t.BaseTx.AssetIDs()
for _, in := range t.Ins {
assets.Add(in.AssetID())
}
return assets
}
var (
errNoImportInputs = errors.New("no import inputs")
)
// SyntacticVerify that this transaction is well-formed.
func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error {
switch {
case t == nil:
return errNilTx
case t.NetID != ctx.NetworkID:
return errWrongNetworkID
case !t.BCID.Equals(ctx.ChainID):
return errWrongChainID
case len(t.Ins) == 0:
return errNoImportInputs
}
fc := ava.NewFlowChecker()
for _, out := range t.Outs {
if err := out.Verify(); err != nil {
return err
}
fc.Produce(out.AssetID(), out.Output().Amount())
}
if !ava.IsSortedTransferableOutputs(t.Outs, c) {
return errOutputsNotSorted
}
for _, in := range t.BaseTx.Ins {
if err := in.Verify(); err != nil {
return err
}
fc.Consume(in.AssetID(), in.Input().Amount())
}
if !ava.IsSortedAndUniqueTransferableInputs(t.BaseTx.Ins) {
return errInputsNotSortedUnique
}
for _, in := range t.Ins {
if err := in.Verify(); err != nil {
return err
}
fc.Consume(in.AssetID(), in.Input().Amount())
}
if !ava.IsSortedAndUniqueTransferableInputs(t.Ins) {
return errInputsNotSortedUnique
}
// TODO: Add the Tx fee to the produced side
return fc.Verify()
}
// SemanticVerify that this transaction is well-formed.
func (t *ImportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error {
if err := t.BaseTx.SemanticVerify(vm, uTx, creds); err != nil {
return err
}
smDB := vm.ctx.SharedMemory.GetDatabase(vm.platform)
defer vm.ctx.SharedMemory.ReleaseDatabase(vm.platform)
state := ava.NewPrefixedState(smDB, vm.codec)
offset := len(t.BaseTx.Ins)
for i, in := range t.Ins {
cred := creds[i+offset]
fxIndex, err := vm.getFx(cred)
if err != nil {
return err
}
fx := vm.fxs[fxIndex].Fx
utxoID := in.UTXOID.InputID()
utxo, err := state.PlatformUTXO(utxoID)
if err != nil {
return err
}
utxoAssetID := utxo.AssetID()
inAssetID := in.AssetID()
if !utxoAssetID.Equals(inAssetID) {
return errAssetIDMismatch
}
if !utxoAssetID.Equals(vm.ava) {
return errWrongAssetID
}
if !vm.verifyFxUsage(fxIndex, inAssetID) {
return errIncompatibleFx
}
if err := fx.VerifyTransfer(uTx, utxo.Out, in.In, cred); err != nil {
return err
}
}
return nil
}
// ExecuteWithSideEffects writes the batch with any additional side effects
func (t *ImportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error {
smDB := vm.ctx.SharedMemory.GetDatabase(vm.platform)
defer vm.ctx.SharedMemory.ReleaseDatabase(vm.platform)
vsmDB := versiondb.New(smDB)
state := ava.NewPrefixedState(vsmDB, vm.codec)
for _, in := range t.Ins {
utxoID := in.UTXOID.InputID()
if err := state.SpendPlatformUTXO(utxoID); err != nil {
return err
}
}
sharedBatch, err := vsmDB.CommitBatch()
if err != nil {
return err
}
return atomic.WriteAll(batch, sharedBatch)
}

366
vms/avm/import_tx_test.go Normal file
View File

@ -0,0 +1,366 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
import (
"bytes"
"testing"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
func TestImportTxSerialization(t *testing.T) {
expected := []byte{
// txID:
0x00, 0x00, 0x00, 0x03,
// networkID:
0x00, 0x00, 0x00, 0x02,
// blockchainID:
0xff, 0xff, 0xff, 0xff, 0xee, 0xee, 0xee, 0xee,
0xdd, 0xdd, 0xdd, 0xdd, 0xcc, 0xcc, 0xcc, 0xcc,
0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa,
0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88,
// number of base outs:
0x00, 0x00, 0x00, 0x00,
// number of base inputs:
0x00, 0x00, 0x00, 0x00,
// number of inputs:
0x00, 0x00, 0x00, 0x01,
// utxoID:
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
// output index
0x00, 0x00, 0x00, 0x00,
// assetID:
0x1f, 0x3f, 0x5f, 0x7f, 0x9e, 0xbe, 0xde, 0xfe,
0x1d, 0x3d, 0x5d, 0x7d, 0x9c, 0xbc, 0xdc, 0xfc,
0x1b, 0x3b, 0x5b, 0x7b, 0x9a, 0xba, 0xda, 0xfa,
0x19, 0x39, 0x59, 0x79, 0x98, 0xb8, 0xd8, 0xf8,
// input:
// input ID:
0x00, 0x00, 0x00, 0x08,
// amount:
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
// num sig indices:
0x00, 0x00, 0x00, 0x01,
// sig index[0]:
0x00, 0x00, 0x00, 0x00,
}
tx := &Tx{UnsignedTx: &ImportTx{
BaseTx: BaseTx{
NetID: 2,
BCID: ids.NewID([32]byte{
0xff, 0xff, 0xff, 0xff, 0xee, 0xee, 0xee, 0xee,
0xdd, 0xdd, 0xdd, 0xdd, 0xcc, 0xcc, 0xcc, 0xcc,
0xbb, 0xbb, 0xbb, 0xbb, 0xaa, 0xaa, 0xaa, 0xaa,
0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88,
}),
},
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{TxID: ids.NewID([32]byte{
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
})},
Asset: ava.Asset{ID: ids.NewID([32]byte{
0x1f, 0x3f, 0x5f, 0x7f, 0x9e, 0xbe, 0xde, 0xfe,
0x1d, 0x3d, 0x5d, 0x7d, 0x9c, 0xbc, 0xdc, 0xfc,
0x1b, 0x3b, 0x5b, 0x7b, 0x9a, 0xba, 0xda, 0xfa,
0x19, 0x39, 0x59, 0x79, 0x98, 0xb8, 0xd8, 0xf8,
})},
In: &secp256k1fx.TransferInput{
Amt: 1000,
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
},
}},
}}
c := codec.NewDefault()
c.RegisterType(&BaseTx{})
c.RegisterType(&CreateAssetTx{})
c.RegisterType(&OperationTx{})
c.RegisterType(&ImportTx{})
c.RegisterType(&ExportTx{})
c.RegisterType(&secp256k1fx.MintOutput{})
c.RegisterType(&secp256k1fx.TransferOutput{})
c.RegisterType(&secp256k1fx.MintInput{})
c.RegisterType(&secp256k1fx.TransferInput{})
c.RegisterType(&secp256k1fx.Credential{})
b, err := c.Marshal(&tx.UnsignedTx)
if err != nil {
t.Fatal(err)
}
tx.Initialize(b)
result := tx.Bytes()
if !bytes.Equal(expected, result) {
t.Fatalf("\nExpected: 0x%x\nResult: 0x%x", expected, result)
}
}
// Test issuing an import transaction.
func TestIssueImportTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t)
issuer := make(chan common.Message, 1)
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
ctx := snow.DefaultContextTest()
ctx.NetworkID = networkID
ctx.ChainID = chainID
ctx.SharedMemory = sm.NewBlockchainSharedMemory(chainID)
genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t)
avaID := genesisTx.ID()
platformID := ids.Empty.Prefix(0)
ctx.Lock.Lock()
vm := &VM{
ava: avaID,
platform: platformID,
}
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
key := keys[0]
utxoID := ava.UTXOID{
TxID: ids.NewID([32]byte{
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
}),
}
tx := &Tx{UnsignedTx: &ImportTx{
BaseTx: BaseTx{
NetID: networkID,
BCID: chainID,
},
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: utxoID,
Asset: ava.Asset{ID: avaID},
In: &secp256k1fx.TransferInput{
Amt: 1000,
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
},
}},
}}
unsignedBytes, err := vm.codec.Marshal(&tx.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)
tx.Creds = append(tx.Creds, &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
})
b, err := vm.codec.Marshal(tx)
if err != nil {
t.Fatal(err)
}
tx.Initialize(b)
if _, err := vm.IssueTx(tx.Bytes(), nil); err == nil {
t.Fatal(err)
}
// Provide the platform UTXO:
smDB := vm.ctx.SharedMemory.GetDatabase(platformID)
utxo := &ava.UTXO{
UTXOID: utxoID,
Asset: ava.Asset{ID: avaID},
Out: &secp256k1fx.TransferOutput{
Amt: 1000,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{key.PublicKey().Address()},
},
},
}
state := ava.NewPrefixedState(smDB, vm.codec)
if err := state.FundPlatformUTXO(utxo); err != nil {
t.Fatal(err)
}
vm.ctx.SharedMemory.ReleaseDatabase(platformID)
if _, err := vm.IssueTx(tx.Bytes(), nil); err != nil {
t.Fatalf("should have issued the transaction correctly but errored: %s", err)
}
ctx.Lock.Unlock()
msg := <-issuer
if msg != common.PendingTxs {
t.Fatalf("Wrong message")
}
txs := vm.PendingTxs()
if len(txs) != 1 {
t.Fatalf("Should have returned %d tx(s)", 1)
}
parsedTx := txs[0]
parsedTx.Accept()
smDB = vm.ctx.SharedMemory.GetDatabase(platformID)
defer vm.ctx.SharedMemory.ReleaseDatabase(platformID)
state = ava.NewPrefixedState(smDB, vm.codec)
if _, err := state.PlatformUTXO(utxoID.InputID()); err == nil {
t.Fatalf("shouldn't have been able to read the utxo")
}
}
// Test force accepting an import transaction.
func TestForceAcceptImportTx(t *testing.T) {
genesisBytes := BuildGenesisTest(t)
issuer := make(chan common.Message, 1)
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
ctx := snow.DefaultContextTest()
ctx.NetworkID = networkID
ctx.ChainID = chainID
ctx.SharedMemory = sm.NewBlockchainSharedMemory(chainID)
platformID := ids.Empty.Prefix(0)
ctx.Lock.Lock()
defer ctx.Lock.Unlock()
vm := &VM{platform: platformID}
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
key := keys[0]
genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t)
utxoID := ava.UTXOID{
TxID: ids.NewID([32]byte{
0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee,
0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec,
0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea,
0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8,
}),
}
tx := &Tx{UnsignedTx: &ImportTx{
BaseTx: BaseTx{
NetID: networkID,
BCID: chainID,
},
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: utxoID,
Asset: ava.Asset{ID: genesisTx.ID()},
In: &secp256k1fx.TransferInput{
Amt: 1000,
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
},
}},
}}
unsignedBytes, err := vm.codec.Marshal(&tx.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)
tx.Creds = append(tx.Creds, &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
})
b, err := vm.codec.Marshal(tx)
if err != nil {
t.Fatal(err)
}
tx.Initialize(b)
parsedTx, err := vm.ParseTx(tx.Bytes())
if err != nil {
t.Fatal(err)
}
if err := parsedTx.Verify(); err == nil {
t.Fatalf("Should have failed verification")
}
parsedTx.Accept()
smDB := vm.ctx.SharedMemory.GetDatabase(platformID)
defer vm.ctx.SharedMemory.ReleaseDatabase(platformID)
state := ava.NewPrefixedState(smDB, vm.codec)
utxoSource := utxoID.InputID()
if _, err := state.PlatformUTXO(utxoSource); err == nil {
t.Fatalf("shouldn't have been able to read the utxo")
}
}

View File

@ -10,6 +10,7 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
"github.com/ava-labs/gecko/vms/secp256k1fx"
@ -52,14 +53,12 @@ func TestInitialStateVerifyNilOutput(t *testing.T) {
func TestInitialStateVerifyInvalidOutput(t *testing.T) {
c := codec.NewDefault()
c.RegisterType(&testVerifiable{})
c.RegisterType(&ava.TestVerifiable{})
numFxs := 1
is := InitialState{
FxID: 0,
Outs: []verify.Verifiable{
&testVerifiable{err: errors.New("")},
},
Outs: []verify.Verifiable{&ava.TestVerifiable{Err: errors.New("")}},
}
if err := is.Verify(c, numFxs); err == nil {
t.Fatalf("Should have errored due to an invalid output")
@ -68,14 +67,14 @@ func TestInitialStateVerifyInvalidOutput(t *testing.T) {
func TestInitialStateVerifyUnsortedOutputs(t *testing.T) {
c := codec.NewDefault()
c.RegisterType(&TestTransferable{})
c.RegisterType(&ava.TestTransferable{})
numFxs := 1
is := InitialState{
FxID: 0,
Outs: []verify.Verifiable{
&TestTransferable{Val: 1},
&TestTransferable{Val: 0},
&ava.TestTransferable{Val: 1},
&ava.TestTransferable{Val: 0},
},
}
if err := is.Verify(c, numFxs); err == nil {

View File

@ -9,7 +9,7 @@ import (
"sort"
"github.com/ava-labs/gecko/utils"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/verify"
)
@ -21,58 +21,9 @@ var (
errNilOperableFxInput = errors.New("nil operable feature extension input is not valid")
)
// OperableOutput ...
type OperableOutput struct {
Out verify.Verifiable `serialize:"true"`
}
// Output returns the feature extension output that this Output is using.
func (out *OperableOutput) Output() verify.Verifiable { return out.Out }
// Verify implements the verify.Verifiable interface
func (out *OperableOutput) Verify() error {
switch {
case out == nil:
return errNilOperableOutput
case out.Out == nil:
return errNilOperableFxOutput
default:
return out.Out.Verify()
}
}
type innerSortOperableOutputs struct {
outs []*OperableOutput
codec codec.Codec
}
func (outs *innerSortOperableOutputs) Less(i, j int) bool {
iOut := outs.outs[i]
jOut := outs.outs[j]
iBytes, err := outs.codec.Marshal(&iOut.Out)
if err != nil {
return false
}
jBytes, err := outs.codec.Marshal(&jOut.Out)
if err != nil {
return false
}
return bytes.Compare(iBytes, jBytes) == -1
}
func (outs *innerSortOperableOutputs) Len() int { return len(outs.outs) }
func (outs *innerSortOperableOutputs) Swap(i, j int) { o := outs.outs; o[j], o[i] = o[i], o[j] }
func sortOperableOutputs(outs []*OperableOutput, c codec.Codec) {
sort.Sort(&innerSortOperableOutputs{outs: outs, codec: c})
}
func isSortedOperableOutputs(outs []*OperableOutput, c codec.Codec) bool {
return sort.IsSorted(&innerSortOperableOutputs{outs: outs, codec: c})
}
// OperableInput ...
type OperableInput struct {
UTXOID `serialize:"true"`
ava.UTXOID `serialize:"true"`
In verify.Verifiable `serialize:"true"`
}

View File

@ -7,76 +7,9 @@ import (
"testing"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/ava"
)
func TestOperableOutputVerifyNil(t *testing.T) {
oo := (*OperableOutput)(nil)
if err := oo.Verify(); err == nil {
t.Fatalf("Should have errored due to nil operable output")
}
}
func TestOperableOutputVerifyNilFx(t *testing.T) {
oo := &OperableOutput{}
if err := oo.Verify(); err == nil {
t.Fatalf("Should have errored due to nil operable fx output")
}
}
func TestOperableOutputVerify(t *testing.T) {
oo := &OperableOutput{
Out: &testVerifiable{},
}
if err := oo.Verify(); err != nil {
t.Fatal(err)
}
if oo.Output() != oo.Out {
t.Fatalf("Should have returned the fx output")
}
}
func TestOperableOutputSorting(t *testing.T) {
c := codec.NewDefault()
c.RegisterType(&TestTransferable{})
c.RegisterType(&testVerifiable{})
outs := []*OperableOutput{
&OperableOutput{
Out: &TestTransferable{Val: 1},
},
&OperableOutput{
Out: &TestTransferable{Val: 0},
},
&OperableOutput{
Out: &TestTransferable{Val: 0},
},
&OperableOutput{
Out: &testVerifiable{},
},
}
if isSortedOperableOutputs(outs, c) {
t.Fatalf("Shouldn't be sorted")
}
sortOperableOutputs(outs, c)
if !isSortedOperableOutputs(outs, c) {
t.Fatalf("Should be sorted")
}
if result := outs[0].Out.(*TestTransferable).Val; result != 0 {
t.Fatalf("Val expected: %d ; result: %d", 0, result)
}
if result := outs[1].Out.(*TestTransferable).Val; result != 0 {
t.Fatalf("Val expected: %d ; result: %d", 0, result)
}
if result := outs[2].Out.(*TestTransferable).Val; result != 1 {
t.Fatalf("Val expected: %d ; result: %d", 0, result)
}
if _, ok := outs[3].Out.(*testVerifiable); !ok {
t.Fatalf("testVerifiable expected")
}
}
func TestOperableInputVerifyNil(t *testing.T) {
oi := (*OperableInput)(nil)
if err := oi.Verify(); err == nil {
@ -93,10 +26,8 @@ func TestOperableInputVerifyNilFx(t *testing.T) {
func TestOperableInputVerify(t *testing.T) {
oi := &OperableInput{
UTXOID: UTXOID{
TxID: ids.Empty,
},
In: &testVerifiable{},
UTXOID: ava.UTXOID{TxID: ids.Empty},
In: &ava.TestVerifiable{},
}
if err := oi.Verify(); err != nil {
t.Fatal(err)
@ -109,32 +40,32 @@ func TestOperableInputVerify(t *testing.T) {
func TestOperableInputSorting(t *testing.T) {
ins := []*OperableInput{
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.NewID([32]byte{1}),
OutputIndex: 1,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.NewID([32]byte{1}),
OutputIndex: 0,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
}
if isSortedAndUniqueOperableInputs(ins) {
@ -163,11 +94,11 @@ func TestOperableInputSorting(t *testing.T) {
t.Fatalf("OutputIndex expected: %s ; result: %s", ids.Empty, result)
}
ins = append(ins, &OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
})
if isSortedAndUniqueOperableInputs(ins) {
t.Fatalf("Shouldn't be unique")

View File

@ -9,7 +9,9 @@ import (
"sort"
"github.com/ava-labs/gecko/utils"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
)
var (
@ -19,10 +21,10 @@ var (
// Operation ...
type Operation struct {
Asset `serialize:"true"`
ava.Asset `serialize:"true"`
Ins []*OperableInput `serialize:"true"`
Outs []*OperableOutput `serialize:"true"`
Outs []verify.Verifiable `serialize:"true"`
}
// Verify implements the verify.Verifiable interface
@ -48,7 +50,7 @@ func (op *Operation) Verify(c codec.Codec) error {
return err
}
}
if !isSortedOperableOutputs(op.Outs, c) {
if !isSortedVerifiables(op.Outs, c) {
return errOutputsNotSorted
}

View File

@ -7,7 +7,9 @@ import (
"testing"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
)
func TestOperationVerifyNil(t *testing.T) {
@ -21,9 +23,7 @@ func TestOperationVerifyNil(t *testing.T) {
func TestOperationVerifyEmpty(t *testing.T) {
c := codec.NewDefault()
op := &Operation{
Asset: Asset{
ID: ids.Empty,
},
Asset: ava.Asset{ID: ids.Empty},
}
if err := op.Verify(c); err == nil {
t.Fatalf("Should have errored due to empty operation")
@ -33,9 +33,7 @@ func TestOperationVerifyEmpty(t *testing.T) {
func TestOperationVerifyInvalidInput(t *testing.T) {
c := codec.NewDefault()
op := &Operation{
Asset: Asset{
ID: ids.Empty,
},
Asset: ava.Asset{ID: ids.Empty},
Ins: []*OperableInput{
&OperableInput{},
},
@ -45,41 +43,24 @@ func TestOperationVerifyInvalidInput(t *testing.T) {
}
}
func TestOperationVerifyInvalidOutput(t *testing.T) {
c := codec.NewDefault()
op := &Operation{
Asset: Asset{
ID: ids.Empty,
},
Outs: []*OperableOutput{
&OperableOutput{},
},
}
if err := op.Verify(c); err == nil {
t.Fatalf("Should have errored due to an invalid output")
}
}
func TestOperationVerifyInputsNotSorted(t *testing.T) {
c := codec.NewDefault()
op := &Operation{
Asset: Asset{
ID: ids.Empty,
},
Asset: ava.Asset{ID: ids.Empty},
Ins: []*OperableInput{
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
},
}
@ -90,19 +71,13 @@ func TestOperationVerifyInputsNotSorted(t *testing.T) {
func TestOperationVerifyOutputsNotSorted(t *testing.T) {
c := codec.NewDefault()
c.RegisterType(&TestTransferable{})
c.RegisterType(&ava.TestTransferable{})
op := &Operation{
Asset: Asset{
ID: ids.Empty,
},
Outs: []*OperableOutput{
&OperableOutput{
Out: &TestTransferable{Val: 1},
},
&OperableOutput{
Out: &TestTransferable{Val: 0},
},
Asset: ava.Asset{ID: ids.Empty},
Outs: []verify.Verifiable{
&ava.TestTransferable{Val: 1},
&ava.TestTransferable{Val: 0},
},
}
if err := op.Verify(c); err == nil {
@ -113,13 +88,9 @@ func TestOperationVerifyOutputsNotSorted(t *testing.T) {
func TestOperationVerify(t *testing.T) {
c := codec.NewDefault()
op := &Operation{
Asset: Asset{
ID: ids.Empty,
},
Outs: []*OperableOutput{
&OperableOutput{
Out: &testVerifiable{},
},
Asset: ava.Asset{ID: ids.Empty},
Outs: []verify.Verifiable{
&ava.TestVerifiable{},
},
}
if err := op.Verify(c); err != nil {
@ -129,34 +100,30 @@ func TestOperationVerify(t *testing.T) {
func TestOperationSorting(t *testing.T) {
c := codec.NewDefault()
c.RegisterType(&testVerifiable{})
c.RegisterType(&ava.TestVerifiable{})
ops := []*Operation{
&Operation{
Asset: Asset{
ID: ids.Empty,
},
Asset: ava.Asset{ID: ids.Empty},
Ins: []*OperableInput{
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
},
},
&Operation{
Asset: Asset{
ID: ids.Empty,
},
Asset: ava.Asset{ID: ids.Empty},
Ins: []*OperableInput{
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
},
},
@ -169,16 +136,14 @@ func TestOperationSorting(t *testing.T) {
t.Fatalf("Should be sorted")
}
ops = append(ops, &Operation{
Asset: Asset{
ID: ids.Empty,
},
Asset: ava.Asset{ID: ids.Empty},
Ins: []*OperableInput{
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
},
})

View File

@ -8,11 +8,14 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
)
var (
errOperationsNotSortedUnique = errors.New("operations not sorted and unique")
errNoOperations = errors.New("an operationTx must have at least one operation")
errDoubleSpend = errors.New("inputs attempt to double spend an input")
)
@ -28,7 +31,7 @@ type OperationTx struct {
func (t *OperationTx) Operations() []*Operation { return t.Ops }
// InputUTXOs track which UTXOs this transaction is consuming.
func (t *OperationTx) InputUTXOs() []*UTXOID {
func (t *OperationTx) InputUTXOs() []*ava.UTXOID {
utxos := t.BaseTx.InputUTXOs()
for _, op := range t.Ops {
for _, in := range op.Ins {
@ -48,22 +51,20 @@ func (t *OperationTx) AssetIDs() ids.Set {
}
// UTXOs returns the UTXOs transaction is producing.
func (t *OperationTx) UTXOs() []*UTXO {
func (t *OperationTx) UTXOs() []*ava.UTXO {
txID := t.ID()
utxos := t.BaseTx.UTXOs()
for _, op := range t.Ops {
asset := op.AssetID()
for _, out := range op.Outs {
utxos = append(utxos, &UTXO{
UTXOID: UTXOID{
utxos = append(utxos, &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: txID,
OutputIndex: uint32(len(utxos)),
},
Asset: Asset{
ID: asset,
},
Out: out.Out,
Asset: ava.Asset{ID: asset},
Out: out,
})
}
}
@ -76,6 +77,8 @@ func (t *OperationTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs i
switch {
case t == nil:
return errNilTx
case len(t.Ops) == 0:
return errNoOperations
}
if err := t.BaseTx.SyntacticVerify(ctx, c, numFxs); err != nil {
@ -106,7 +109,7 @@ func (t *OperationTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs i
}
// SemanticVerify that this transaction is well-formed.
func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) error {
func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error {
if err := t.BaseTx.SemanticVerify(vm, uTx, creds); err != nil {
return err
}
@ -123,40 +126,13 @@ func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential)
ins = append(ins, in.In)
cred := creds[i+offset]
credIntfs = append(credIntfs, cred.Cred)
credIntfs = append(credIntfs, cred)
utxoID := in.InputID()
utxo, err := vm.state.UTXO(utxoID)
if err == nil {
utxoAssetID := utxo.AssetID()
if !utxoAssetID.Equals(opAssetID) {
return errAssetIDMismatch
utxo, err := vm.getUTXO(&in.UTXOID)
if err != nil {
return err
}
utxos = append(utxos, utxo.Out)
continue
}
inputTx, inputIndex := in.InputSource()
parent := UniqueTx{
vm: vm,
txID: inputTx,
}
if err := parent.Verify(); err != nil {
return errMissingUTXO
} else if status := parent.Status(); status.Decided() {
return errMissingUTXO
}
parentUTXOs := parent.UTXOs()
if uint32(len(parentUTXOs)) <= inputIndex || int(inputIndex) < 0 {
return errInvalidUTXO
}
utxo = parentUTXOs[int(inputIndex)]
utxoAssetID := utxo.AssetID()
if !utxoAssetID.Equals(opAssetID) {
return errAssetIDMismatch
@ -165,7 +141,7 @@ func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential)
}
offset += len(op.Ins)
for _, out := range op.Outs {
outs = append(outs, out.Out)
outs = append(outs, out)
}
var fxObj interface{}

View File

@ -8,6 +8,7 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/vms/components/ava"
)
const (
@ -37,31 +38,31 @@ func (s *prefixedState) UniqueTx(tx *UniqueTx) *UniqueTx {
}
// Tx attempts to load a transaction from storage.
func (s *prefixedState) Tx(id ids.ID) (*Tx, error) { return s.state.Tx(s.uniqueID(id, txID, s.tx)) }
func (s *prefixedState) Tx(id ids.ID) (*Tx, error) { return s.state.Tx(uniqueID(id, txID, s.tx)) }
// SetTx saves the provided transaction to storage.
func (s *prefixedState) SetTx(id ids.ID, tx *Tx) error {
return s.state.SetTx(s.uniqueID(id, txID, s.tx), tx)
return s.state.SetTx(uniqueID(id, txID, s.tx), tx)
}
// UTXO attempts to load a utxo from storage.
func (s *prefixedState) UTXO(id ids.ID) (*UTXO, error) {
return s.state.UTXO(s.uniqueID(id, utxoID, s.utxo))
func (s *prefixedState) UTXO(id ids.ID) (*ava.UTXO, error) {
return s.state.UTXO(uniqueID(id, utxoID, s.utxo))
}
// SetUTXO saves the provided utxo to storage.
func (s *prefixedState) SetUTXO(id ids.ID, utxo *UTXO) error {
return s.state.SetUTXO(s.uniqueID(id, utxoID, s.utxo), utxo)
func (s *prefixedState) SetUTXO(id ids.ID, utxo *ava.UTXO) error {
return s.state.SetUTXO(uniqueID(id, utxoID, s.utxo), utxo)
}
// Status returns the status of the provided transaction id from storage.
func (s *prefixedState) Status(id ids.ID) (choices.Status, error) {
return s.state.Status(s.uniqueID(id, txStatusID, s.txStatus))
return s.state.Status(uniqueID(id, txStatusID, s.txStatus))
}
// SetStatus saves the provided status to storage.
func (s *prefixedState) SetStatus(id ids.ID, status choices.Status) error {
return s.state.SetStatus(s.uniqueID(id, txStatusID, s.txStatus), status)
return s.state.SetStatus(uniqueID(id, txStatusID, s.txStatus), status)
}
// DBInitialized returns the status of this database. If the database is
@ -76,21 +77,12 @@ func (s *prefixedState) SetDBInitialized(status choices.Status) error {
// Funds returns the mapping from the 32 byte representation of an address to a
// list of utxo IDs that reference the address.
func (s *prefixedState) Funds(id ids.ID) ([]ids.ID, error) {
return s.state.IDs(s.uniqueID(id, fundsID, s.funds))
return s.state.IDs(uniqueID(id, fundsID, s.funds))
}
// SetFunds saves the mapping from address to utxo IDs to storage.
func (s *prefixedState) SetFunds(id ids.ID, idSlice []ids.ID) error {
return s.state.SetIDs(s.uniqueID(id, fundsID, s.funds), idSlice)
}
func (s *prefixedState) uniqueID(id ids.ID, prefix uint64, cacher cache.Cacher) ids.ID {
if cachedIDIntf, found := cacher.Get(id); found {
return cachedIDIntf.(ids.ID)
}
uID := id.Prefix(prefix)
cacher.Put(id, uID)
return uID
return s.state.SetIDs(uniqueID(id, fundsID, s.funds), idSlice)
}
// SpendUTXO consumes the provided utxo.
@ -103,7 +95,7 @@ func (s *prefixedState) SpendUTXO(utxoID ids.ID) error {
return err
}
addressable, ok := utxo.Out.(FxAddressable)
addressable, ok := utxo.Out.(ava.Addressable)
if !ok {
return nil
}
@ -126,13 +118,13 @@ func (s *prefixedState) removeUTXO(addrs [][]byte, utxoID ids.ID) error {
}
// FundUTXO adds the provided utxo to the database
func (s *prefixedState) FundUTXO(utxo *UTXO) error {
func (s *prefixedState) FundUTXO(utxo *ava.UTXO) error {
utxoID := utxo.InputID()
if err := s.SetUTXO(utxoID, utxo); err != nil {
return err
}
addressable, ok := utxo.Out.(FxAddressable)
addressable, ok := utxo.Out.(ava.Addressable)
if !ok {
return nil
}

View File

@ -11,6 +11,7 @@ import (
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/units"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
@ -18,29 +19,26 @@ func TestPrefixedSetsAndGets(t *testing.T) {
vm := GenesisVM(t)
state := vm.state
vm.codec.RegisterType(&testVerifiable{})
vm.codec.RegisterType(&ava.TestVerifiable{})
utxo := &UTXO{
UTXOID: UTXOID{
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
Asset: Asset{ID: ids.Empty},
Out: &testVerifiable{},
Asset: ava.Asset{ID: ids.Empty},
Out: &ava.TestVerifiable{},
}
tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{
tx := &Tx{UnsignedTx: &BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
Asset: Asset{
ID: asset,
},
Asset: ava.Asset{ID: asset},
In: &secp256k1fx.TransferInput{
Amt: 20 * units.KiloAva,
Input: secp256k1fx.Input{
@ -49,9 +47,8 @@ func TestPrefixedSetsAndGets(t *testing.T) {
},
},
},
},
},
}}}
}},
}}
unsignedBytes, err := vm.codec.Marshal(tx.UnsignedTx)
if err != nil {
@ -66,12 +63,10 @@ func TestPrefixedSetsAndGets(t *testing.T) {
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
tx.Creds = append(tx.Creds, &Credential{
Cred: &secp256k1fx.Credential{
tx.Creds = append(tx.Creds, &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
},
})
b, err := vm.codec.Marshal(tx)
@ -118,15 +113,15 @@ func TestPrefixedFundingNoAddresses(t *testing.T) {
vm := GenesisVM(t)
state := vm.state
vm.codec.RegisterType(&testVerifiable{})
vm.codec.RegisterType(&ava.TestVerifiable{})
utxo := &UTXO{
UTXOID: UTXOID{
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
Asset: Asset{ID: ids.Empty},
Out: &testVerifiable{},
Asset: ava.Asset{ID: ids.Empty},
Out: &ava.TestVerifiable{},
}
if err := state.FundUTXO(utxo); err != nil {
@ -143,12 +138,12 @@ func TestPrefixedFundingAddresses(t *testing.T) {
vm.codec.RegisterType(&testAddressable{})
utxo := &UTXO{
UTXOID: UTXOID{
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
Asset: Asset{ID: ids.Empty},
Asset: ava.Asset{ID: ids.Empty},
Out: &testAddressable{
Addrs: [][]byte{
[]byte{0},

View File

@ -8,16 +8,15 @@ import (
"errors"
"fmt"
"net/http"
"sort"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/json"
"github.com/ava-labs/gecko/utils/math"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/verify"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
@ -75,6 +74,10 @@ type GetTxStatusReply struct {
Status choices.Status `json:"status"`
}
var (
errNilTxID = errors.New("nil transaction ID")
)
// GetTxStatus returns the status of the specified transaction
func (service *Service) GetTxStatus(r *http.Request, args *GetTxStatusArgs, reply *GetTxStatusReply) error {
service.vm.ctx.Log.Verbo("GetTxStatus called with %s", args.TxID)
@ -163,7 +166,7 @@ func (service *Service) GetAssetDescription(_ *http.Request, args *GetAssetDescr
if status := tx.Status(); !status.Fetched() {
return errUnknownAssetID
}
createAssetTx, ok := tx.t.tx.UnsignedTx.(*CreateAssetTx)
createAssetTx, ok := tx.UnsignedTx.(*CreateAssetTx)
if !ok {
return errTxNotCreateAsset
}
@ -214,7 +217,7 @@ func (service *Service) GetBalance(r *http.Request, args *GetBalanceArgs, reply
for _, utxo := range utxos {
if utxo.AssetID().Equals(assetID) {
transferable, ok := utxo.Out.(FxTransferable)
transferable, ok := utxo.Out.(ava.Transferable)
if !ok {
continue
}
@ -596,7 +599,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
amountSpent := uint64(0)
time := service.vm.clock.Unix()
ins := []*TransferableInput{}
ins := []*ava.TransferableInput{}
keys := [][]*crypto.PrivateKeySECP256K1R{}
for _, utxo := range utxos {
if !utxo.AssetID().Equals(assetID) {
@ -606,7 +609,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
if err != nil {
continue
}
input, ok := inputIntf.(FxTransferable)
input, ok := inputIntf.(ava.Transferable)
if !ok {
continue
}
@ -616,9 +619,9 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
}
amountSpent = spent
in := &TransferableInput{
in := &ava.TransferableInput{
UTXOID: utxo.UTXOID,
Asset: Asset{ID: assetID},
Asset: ava.Asset{ID: assetID},
In: input,
}
@ -634,13 +637,10 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
return errInsufficientFunds
}
SortTransferableInputsWithSigners(ins, keys)
ava.SortTransferableInputsWithSigners(ins, keys)
outs := []*TransferableOutput{
&TransferableOutput{
Asset: Asset{
ID: assetID,
},
outs := []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{
Amt: uint64(args.Amount),
Locktime: 0,
@ -649,16 +649,12 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
Addrs: []ids.ShortID{to},
},
},
},
}
}}
if amountSpent > uint64(args.Amount) {
changeAddr := kc.Keys[0].PublicKey().Address()
outs = append(outs,
&TransferableOutput{
Asset: Asset{
ID: assetID,
},
outs = append(outs, &ava.TransferableOutput{
Asset: ava.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{
Amt: amountSpent - uint64(args.Amount),
Locktime: 0,
@ -667,11 +663,10 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
Addrs: []ids.ShortID{changeAddr},
},
},
},
)
})
}
SortTransferableOutputs(outs, service.vm.codec)
ava.SortTransferableOutputs(outs, service.vm.codec)
tx := Tx{
UnsignedTx: &BaseTx{
@ -700,7 +695,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
cred.Sigs = append(cred.Sigs, fixedSig)
}
tx.Creds = append(tx.Creds, &Credential{Cred: cred})
tx.Creds = append(tx.Creds, cred)
}
b, err := service.vm.codec.Marshal(tx)
@ -717,42 +712,6 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply)
return nil
}
type innerSortTransferableInputsWithSigners struct {
ins []*TransferableInput
signers [][]*crypto.PrivateKeySECP256K1R
}
func (ins *innerSortTransferableInputsWithSigners) Less(i, j int) bool {
iID, iIndex := ins.ins[i].InputSource()
jID, jIndex := ins.ins[j].InputSource()
switch bytes.Compare(iID.Bytes(), jID.Bytes()) {
case -1:
return true
case 0:
return iIndex < jIndex
default:
return false
}
}
func (ins *innerSortTransferableInputsWithSigners) Len() int { return len(ins.ins) }
func (ins *innerSortTransferableInputsWithSigners) Swap(i, j int) {
ins.ins[j], ins.ins[i] = ins.ins[i], ins.ins[j]
ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j]
}
// SortTransferableInputsWithSigners sorts the inputs and signers based on the
// input's utxo ID
func SortTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) {
sort.Sort(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
}
// IsSortedAndUniqueTransferableInputsWithSigners returns true if the inputs are
// sorted and unique
func IsSortedAndUniqueTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) bool {
return utils.IsSortedAndUnique(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
}
// CreateMintTxArgs are arguments for passing into CreateMintTx requests
type CreateMintTxArgs struct {
Amount json.Uint64 `json:"amount"`
@ -836,9 +795,7 @@ func (service *Service) CreateMintTx(r *http.Request, args *CreateMintTxArgs, re
},
Ops: []*Operation{
&Operation{
Asset: Asset{
ID: assetID,
},
Asset: ava.Asset{ID: assetID},
Ins: []*OperableInput{
&OperableInput{
UTXOID: utxo.UTXOID,
@ -849,13 +806,10 @@ func (service *Service) CreateMintTx(r *http.Request, args *CreateMintTxArgs, re
},
},
},
Outs: []*OperableOutput{
&OperableOutput{
Outs: []verify.Verifiable{
&secp256k1fx.MintOutput{
OutputOwners: out.OutputOwners,
},
},
&OperableOutput{
&secp256k1fx.TransferOutput{
Amt: uint64(args.Amount),
OutputOwners: secp256k1fx.OutputOwners{
@ -867,7 +821,6 @@ func (service *Service) CreateMintTx(r *http.Request, args *CreateMintTxArgs, re
},
},
},
},
}
txBytes, err := service.vm.codec.Marshal(&tx)
@ -963,11 +916,11 @@ func (service *Service) SignMintTx(r *http.Request, args *SignMintTxArgs, reply
}
if len(tx.Creds) == 0 {
tx.Creds = append(tx.Creds, &Credential{Cred: &secp256k1fx.Credential{}})
tx.Creds = append(tx.Creds, &secp256k1fx.Credential{})
}
cred := tx.Creds[0]
switch cred := cred.Cred.(type) {
switch cred := cred.(type) {
case *secp256k1fx.Credential:
if len(cred.Sigs) != size {
cred.Sigs = make([][crypto.SECP256K1RSigLen]byte, size)
@ -994,3 +947,317 @@ func (service *Service) SignMintTx(r *http.Request, args *SignMintTxArgs, reply
reply.Tx.Bytes = txBytes
return nil
}
// SendImportArgs are arguments for passing into SendImport requests
type SendImportArgs struct {
Username string `json:"username"`
Password string `json:"password"`
To string `json:"to"`
}
// SendImportReply defines the SendImport replies returned from the API
type SendImportReply struct {
TxID ids.ID `json:"txID"`
}
// SendImport returns the ID of the newly created atomic transaction
func (service *Service) SendImport(_ *http.Request, args *SendImportArgs, reply *SendImportReply) error {
service.vm.ctx.Log.Verbo("SendExport called with username: %s", args.Username)
toBytes, err := service.vm.Parse(args.To)
if err != nil {
return fmt.Errorf("problem parsing to address: %w", err)
}
to, err := ids.ToShortID(toBytes)
if err != nil {
return fmt.Errorf("problem parsing to address: %w", err)
}
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
if err != nil {
return fmt.Errorf("problem retrieving user: %w", err)
}
user := userState{vm: service.vm}
addresses, _ := user.Addresses(db)
addrs := ids.Set{}
addrs.Add(addresses...)
utxos, err := service.vm.GetAtomicUTXOs(addrs)
if err != nil {
return fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err)
}
kc := secp256k1fx.NewKeychain()
for _, addr := range addresses {
sk, err := user.Key(db, addr)
if err != nil {
return fmt.Errorf("problem retrieving private key: %w", err)
}
kc.Add(sk)
}
amount := uint64(0)
time := service.vm.clock.Unix()
ins := []*ava.TransferableInput{}
keys := [][]*crypto.PrivateKeySECP256K1R{}
for _, utxo := range utxos {
if !utxo.AssetID().Equals(service.vm.ava) {
continue
}
inputIntf, signers, err := kc.Spend(utxo.Out, time)
if err != nil {
continue
}
input, ok := inputIntf.(ava.Transferable)
if !ok {
continue
}
spent, err := math.Add64(amount, input.Amount())
if err != nil {
return errSpendOverflow
}
amount = spent
in := &ava.TransferableInput{
UTXOID: utxo.UTXOID,
Asset: ava.Asset{ID: service.vm.ava},
In: input,
}
ins = append(ins, in)
keys = append(keys, signers)
}
ava.SortTransferableInputsWithSigners(ins, keys)
outs := []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{ID: service.vm.ava},
Out: &secp256k1fx.TransferOutput{
Amt: amount,
Locktime: 0,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{to},
},
},
}}
tx := Tx{UnsignedTx: &ImportTx{
BaseTx: BaseTx{
NetID: service.vm.ctx.NetworkID,
BCID: service.vm.ctx.ChainID,
Outs: outs,
},
Ins: ins,
}}
unsignedBytes, err := service.vm.codec.Marshal(&tx.UnsignedTx)
if err != nil {
return fmt.Errorf("problem creating transaction: %w", 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 fmt.Errorf("problem creating transaction: %w", err)
}
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
cred.Sigs = append(cred.Sigs, fixedSig)
}
tx.Creds = append(tx.Creds, cred)
}
b, err := service.vm.codec.Marshal(tx)
if err != nil {
return fmt.Errorf("problem creating transaction: %w", err)
}
txID, err := service.vm.IssueTx(b, nil)
if err != nil {
return fmt.Errorf("problem issuing transaction: %w", err)
}
reply.TxID = txID
return nil
}
// SendExportArgs are arguments for passing into SendExport requests
type SendExportArgs struct {
Username string `json:"username"`
Password string `json:"password"`
Amount json.Uint64 `json:"amount"`
To string `json:"to"`
}
// SendExportReply defines the Send replies returned from the API
type SendExportReply struct {
TxID ids.ID `json:"txID"`
}
// SendExport returns the ID of the newly created atomic transaction
func (service *Service) SendExport(_ *http.Request, args *SendExportArgs, reply *SendExportReply) error {
service.vm.ctx.Log.Verbo("SendExport called with username: %s", args.Username)
if args.Amount == 0 {
return errInvalidAmount
}
toBytes, err := service.vm.Parse(args.To)
if err != nil {
return fmt.Errorf("problem parsing to address: %w", err)
}
to, err := ids.ToShortID(toBytes)
if err != nil {
return fmt.Errorf("problem parsing to address: %w", err)
}
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
if err != nil {
return fmt.Errorf("problem retrieving user: %w", err)
}
user := userState{vm: service.vm}
addresses, _ := user.Addresses(db)
addrs := ids.Set{}
addrs.Add(addresses...)
utxos, err := service.vm.GetUTXOs(addrs)
if err != nil {
return fmt.Errorf("problem retrieving user's UTXOs: %w", err)
}
kc := secp256k1fx.NewKeychain()
for _, addr := range addresses {
sk, err := user.Key(db, addr)
if err != nil {
return fmt.Errorf("problem retrieving private key: %w", err)
}
kc.Add(sk)
}
amountSpent := uint64(0)
time := service.vm.clock.Unix()
ins := []*ava.TransferableInput{}
keys := [][]*crypto.PrivateKeySECP256K1R{}
for _, utxo := range utxos {
if !utxo.AssetID().Equals(service.vm.ava) {
continue
}
inputIntf, signers, err := kc.Spend(utxo.Out, time)
if err != nil {
continue
}
input, ok := inputIntf.(ava.Transferable)
if !ok {
continue
}
spent, err := math.Add64(amountSpent, input.Amount())
if err != nil {
return errSpendOverflow
}
amountSpent = spent
in := &ava.TransferableInput{
UTXOID: utxo.UTXOID,
Asset: ava.Asset{ID: service.vm.ava},
In: input,
}
ins = append(ins, in)
keys = append(keys, signers)
if amountSpent >= uint64(args.Amount) {
break
}
}
if amountSpent < uint64(args.Amount) {
return errInsufficientFunds
}
ava.SortTransferableInputsWithSigners(ins, keys)
exportOuts := []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{ID: service.vm.ava},
Out: &secp256k1fx.TransferOutput{
Amt: uint64(args.Amount),
Locktime: 0,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{to},
},
},
}}
outs := []*ava.TransferableOutput{}
if amountSpent > uint64(args.Amount) {
changeAddr := kc.Keys[0].PublicKey().Address()
outs = append(outs, &ava.TransferableOutput{
Asset: ava.Asset{ID: service.vm.ava},
Out: &secp256k1fx.TransferOutput{
Amt: amountSpent - uint64(args.Amount),
Locktime: 0,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{changeAddr},
},
},
})
}
ava.SortTransferableOutputs(outs, service.vm.codec)
tx := Tx{UnsignedTx: &ExportTx{
BaseTx: BaseTx{
NetID: service.vm.ctx.NetworkID,
BCID: service.vm.ctx.ChainID,
Outs: outs,
Ins: ins,
},
Outs: exportOuts,
}}
unsignedBytes, err := service.vm.codec.Marshal(&tx.UnsignedTx)
if err != nil {
return fmt.Errorf("problem creating transaction: %w", 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 fmt.Errorf("problem creating transaction: %w", err)
}
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
cred.Sigs = append(cred.Sigs, fixedSig)
}
tx.Creds = append(tx.Creds, cred)
}
b, err := service.vm.codec.Marshal(tx)
if err != nil {
return fmt.Errorf("problem creating transaction: %w", err)
}
txID, err := service.vm.IssueTx(b, nil)
if err != nil {
return fmt.Errorf("problem issuing transaction: %w", err)
}
reply.TxID = txID
return nil
}

View File

@ -136,7 +136,7 @@ func TestCreateFixedCapAsset(t *testing.T) {
t.Fatal(err)
}
if reply.AssetID.String() != "27ySRc5CE4obYwkS6kyvj5S8eGxGkr994157Hdo82mKVHTWpUT" {
if reply.AssetID.String() != "2PEdmaGjKsSd14xPHZkVjhDdxH1VBsCATW8gnmqfMYfp68EwU5" {
t.Fatalf("Wrong assetID returned from CreateFixedCapAsset %s", reply.AssetID)
}
}
@ -182,7 +182,7 @@ func TestCreateVariableCapAsset(t *testing.T) {
t.Fatal(err)
}
if reply.AssetID.String() != "2vnRkWvRN3G9JJ7pixBmNdq4pfwRFkpew4kccf27WokYLH9VYY" {
if reply.AssetID.String() != "p58dzpQikQmKVp3QtXMrg4e9AcppDc7MgqjpQf18CiNrpr2ug" {
t.Fatalf("Wrong assetID returned from CreateFixedCapAsset %s", reply.AssetID)
}
}

View File

@ -8,169 +8,58 @@ import (
"github.com/ava-labs/gecko/cache"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/vms/components/ava"
)
var (
errCacheTypeMismatch = errors.New("type returned from cache doesn't match the expected type")
)
func uniqueID(id ids.ID, prefix uint64, cacher cache.Cacher) ids.ID {
if cachedIDIntf, found := cacher.Get(id); found {
return cachedIDIntf.(ids.ID)
}
uID := id.Prefix(prefix)
cacher.Put(id, uID)
return uID
}
// state is a thin wrapper around a database to provide, caching, serialization,
// and de-serialization.
type state struct {
c cache.Cacher
vm *VM
}
type state struct{ ava.State }
// Tx attempts to load a transaction from storage.
func (s *state) Tx(id ids.ID) (*Tx, error) {
if txIntf, found := s.c.Get(id); found {
if txIntf, found := s.Cache.Get(id); found {
if tx, ok := txIntf.(*Tx); ok {
return tx, nil
}
return nil, errCacheTypeMismatch
}
bytes, err := s.vm.db.Get(id.Bytes())
bytes, err := s.DB.Get(id.Bytes())
if err != nil {
return nil, err
}
// The key was in the database
tx := &Tx{}
if err := s.vm.codec.Unmarshal(bytes, tx); err != nil {
if err := s.Codec.Unmarshal(bytes, tx); err != nil {
return nil, err
}
tx.Initialize(bytes)
s.c.Put(id, tx)
s.Cache.Put(id, tx)
return tx, nil
}
// SetTx saves the provided transaction to storage.
func (s *state) SetTx(id ids.ID, tx *Tx) error {
if tx == nil {
s.c.Evict(id)
return s.vm.db.Delete(id.Bytes())
s.Cache.Evict(id)
return s.DB.Delete(id.Bytes())
}
s.c.Put(id, tx)
return s.vm.db.Put(id.Bytes(), tx.Bytes())
}
// UTXO attempts to load a utxo from storage.
func (s *state) UTXO(id ids.ID) (*UTXO, error) {
if utxoIntf, found := s.c.Get(id); found {
if utxo, ok := utxoIntf.(*UTXO); ok {
return utxo, nil
}
return nil, errCacheTypeMismatch
}
bytes, err := s.vm.db.Get(id.Bytes())
if err != nil {
return nil, err
}
// The key was in the database
utxo := &UTXO{}
if err := s.vm.codec.Unmarshal(bytes, utxo); err != nil {
return nil, err
}
s.c.Put(id, utxo)
return utxo, nil
}
// SetUTXO saves the provided utxo to storage.
func (s *state) SetUTXO(id ids.ID, utxo *UTXO) error {
if utxo == nil {
s.c.Evict(id)
return s.vm.db.Delete(id.Bytes())
}
bytes, err := s.vm.codec.Marshal(utxo)
if err != nil {
return err
}
s.c.Put(id, utxo)
return s.vm.db.Put(id.Bytes(), bytes)
}
// Status returns a status from storage.
func (s *state) Status(id ids.ID) (choices.Status, error) {
if statusIntf, found := s.c.Get(id); found {
if status, ok := statusIntf.(choices.Status); ok {
return status, nil
}
return choices.Unknown, errCacheTypeMismatch
}
bytes, err := s.vm.db.Get(id.Bytes())
if err != nil {
return choices.Unknown, err
}
var status choices.Status
s.vm.codec.Unmarshal(bytes, &status)
s.c.Put(id, status)
return status, nil
}
// SetStatus saves a status in storage.
func (s *state) SetStatus(id ids.ID, status choices.Status) error {
if status == choices.Unknown {
s.c.Evict(id)
return s.vm.db.Delete(id.Bytes())
}
s.c.Put(id, status)
bytes, err := s.vm.codec.Marshal(status)
if err != nil {
return err
}
return s.vm.db.Put(id.Bytes(), bytes)
}
// IDs returns a slice of IDs from storage
func (s *state) IDs(id ids.ID) ([]ids.ID, error) {
if idsIntf, found := s.c.Get(id); found {
if idSlice, ok := idsIntf.([]ids.ID); ok {
return idSlice, nil
}
return nil, errCacheTypeMismatch
}
bytes, err := s.vm.db.Get(id.Bytes())
if err != nil {
return nil, err
}
idSlice := []ids.ID(nil)
if err := s.vm.codec.Unmarshal(bytes, &idSlice); err != nil {
return nil, err
}
s.c.Put(id, idSlice)
return idSlice, nil
}
// SetIDs saves a slice of IDs to the database.
func (s *state) SetIDs(id ids.ID, idSlice []ids.ID) error {
if len(idSlice) == 0 {
s.c.Evict(id)
return s.vm.db.Delete(id.Bytes())
}
s.c.Put(id, idSlice)
bytes, err := s.vm.codec.Marshal(idSlice)
if err != nil {
return err
}
return s.vm.db.Put(id.Bytes(), bytes)
s.Cache.Put(id, tx)
return s.DB.Put(id.Bytes(), tx.Bytes())
}

View File

@ -10,6 +10,7 @@ import (
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/units"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
@ -67,7 +68,7 @@ func TestStateIDs(t *testing.T) {
}
}
state.c.Flush()
state.Cache.Flush()
result, err = state.IDs(ids.Empty)
if err != nil {
@ -94,7 +95,7 @@ func TestStateIDs(t *testing.T) {
t.Fatalf("Should have errored during cache lookup")
}
state.c.Flush()
state.Cache.Flush()
result, err = state.IDs(ids.Empty)
if err == nil {
@ -174,19 +175,19 @@ func TestStateUTXOs(t *testing.T) {
vm := GenesisVM(t)
state := vm.state.state
vm.codec.RegisterType(&testVerifiable{})
vm.codec.RegisterType(&ava.TestVerifiable{})
if _, err := state.UTXO(ids.Empty); err == nil {
t.Fatalf("Should have errored when reading utxo")
}
utxo := &UTXO{
UTXOID: UTXOID{
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
Asset: Asset{ID: ids.Empty},
Out: &testVerifiable{},
Asset: ava.Asset{ID: ids.Empty},
Out: &ava.TestVerifiable{},
}
if err := state.SetUTXO(ids.Empty, utxo); err != nil {
@ -202,7 +203,7 @@ func TestStateUTXOs(t *testing.T) {
t.Fatalf("Wrong UTXO returned")
}
state.c.Flush()
state.Cache.Flush()
result, err = state.UTXO(ids.Empty)
if err != nil {
@ -221,7 +222,7 @@ func TestStateUTXOs(t *testing.T) {
t.Fatalf("Should have errored when reading utxo")
}
if err := state.SetUTXO(ids.Empty, &UTXO{}); err == nil {
if err := state.SetUTXO(ids.Empty, &ava.UTXO{}); err == nil {
t.Fatalf("Should have errored packing the utxo")
}
@ -233,7 +234,7 @@ func TestStateUTXOs(t *testing.T) {
t.Fatalf("Should have errored when reading utxo")
}
state.c.Flush()
state.Cache.Flush()
if _, err := state.UTXO(ids.Empty); err == nil {
t.Fatalf("Should have errored when reading utxo")
@ -244,24 +245,21 @@ func TestStateTXs(t *testing.T) {
vm := GenesisVM(t)
state := vm.state.state
vm.codec.RegisterType(&TestTransferable{})
vm.codec.RegisterType(&ava.TestTransferable{})
if _, err := state.Tx(ids.Empty); err == nil {
t.Fatalf("Should have errored when reading tx")
}
tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{
tx := &Tx{UnsignedTx: &BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
Asset: Asset{
ID: asset,
},
Asset: ava.Asset{ID: asset},
In: &secp256k1fx.TransferInput{
Amt: 20 * units.KiloAva,
Input: secp256k1fx.Input{
@ -270,9 +268,8 @@ func TestStateTXs(t *testing.T) {
},
},
},
},
},
}}}
}},
}}
unsignedBytes, err := vm.codec.Marshal(tx.UnsignedTx)
if err != nil {
@ -287,12 +284,10 @@ func TestStateTXs(t *testing.T) {
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
tx.Creds = append(tx.Creds, &Credential{
Cred: &secp256k1fx.Credential{
tx.Creds = append(tx.Creds, &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
},
})
b, err := vm.codec.Marshal(tx)
@ -314,7 +309,7 @@ func TestStateTXs(t *testing.T) {
t.Fatalf("Wrong Tx returned")
}
state.c.Flush()
state.Cache.Flush()
result, err = state.Tx(ids.Empty)
if err != nil {
@ -341,7 +336,7 @@ func TestStateTXs(t *testing.T) {
t.Fatalf("Should have errored when reading tx")
}
state.c.Flush()
state.Cache.Flush()
if _, err := state.Tx(ids.Empty); err == nil {
t.Fatalf("Should have errored when reading tx")

View File

@ -48,6 +48,8 @@ func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, repl
c.RegisterType(&BaseTx{})
c.RegisterType(&CreateAssetTx{})
c.RegisterType(&OperationTx{})
c.RegisterType(&ImportTx{})
c.RegisterType(&ExportTx{})
c.RegisterType(&secp256k1fx.MintOutput{})
c.RegisterType(&secp256k1fx.TransferOutput{})
c.RegisterType(&secp256k1fx.MintInput{})

View File

@ -80,19 +80,88 @@ func TestBuildGenesis(t *testing.T) {
t.Fatal(err)
}
expected := "1112YAVd1YsJ7JBDMQssciuuu9ySgebznWfmfT8JSw5vUKERtP4WGyitE7z38J8tExNmvK2kuwHsUP3erfcncXBWmJkdnd9nDJoj9tCiQHJmW1pstNQn3zXHdTnw6KJcG8Ro36ahknQkuy9ZSXgnZtpFhqUuwSd7mPj8vzZcqJMXLXorCBfvhwypTbZKogM9tUshyUfngfkg256ZsoU2ufMjhTG14PBBrgJkXD2F38uVSXWvYbubMVWDZbDnUzbyD3Azrs2Hydf8Paio6aNjwfwc1py61oXS5ehC55wiYbKpfzwE4px3bfYBu9yV6rvhivksB56vop9LEo8Pdo71tFAMkhR5toZmYcqRKyLXAnYqonUgmPsyxNwU22as8oscT5dj3Qxy1jsg6bEp6GwQepNqsWufGYx6Hiby2r5hyRZeYdk6xsXMPGBSBWUXhKX3ReTxBnjcrVE2Zc3G9eMvRho1tKzt7ppkutpcQemdDy2dxGryMqaFmPJaTaqcH2vB197KgVFbPgmHZY3ufUdfpVzzHax365pwCmzQD2PQh8hCqEP7rfV5e8uXKQiSynngoNDM4ak145zTpcUaX8htMGinfs45aKQvo5WHcD6ccRnHzc7dyXN8xJRnMznsuRN7D6k66DdbfDYhc2NbVUgXRAF4wSNTtsuZGxCGTEjQyYaoUoJowGXvnxmXAWHvLyMJswNizBeYgw1agRg5qB4AEKX96BFXhJq3MbsBRiypLR6nSuZgPFhCrLdBtstxEC2SPQNuUVWW9Qy68dDWQ3Fxx95n1pnjVru9wDJFoemg2imXRR"
expected := formatting.CB58{Bytes: []byte{
0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x61, 0x73,
0x73, 0x65, 0x74, 0x31, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0f, 0x6d, 0x79, 0x46, 0x69, 0x78, 0x65,
0x64, 0x43, 0x61, 0x70, 0x41, 0x73, 0x73, 0x65,
0x74, 0x00, 0x04, 0x4d, 0x46, 0x43, 0x41, 0x08,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x50,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x3f, 0x78, 0xe5, 0x10, 0xdf, 0x62, 0xbc, 0x48,
0xb0, 0x82, 0x9e, 0xc0, 0x6d, 0x6a, 0x6b, 0x98,
0x06, 0x2d, 0x69, 0x53, 0x00, 0x00, 0x00, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x50,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0xc5, 0x49, 0x03, 0xde, 0x51, 0x77, 0xa1, 0x6f,
0x78, 0x11, 0x77, 0x1e, 0xf2, 0xf4, 0x65, 0x9d,
0x9e, 0x86, 0x46, 0x71, 0x00, 0x00, 0x00, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xa0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x3f, 0x58, 0xfd, 0xa2, 0xe9, 0xea, 0x8d, 0x9e,
0x4b, 0x18, 0x18, 0x32, 0xa0, 0x7b, 0x26, 0xda,
0xe2, 0x86, 0xf2, 0xcb, 0x00, 0x00, 0x00, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xa0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x64, 0x59, 0x38, 0xbb, 0x7a, 0xe2, 0x19, 0x32,
0x70, 0xe6, 0xff, 0xef, 0x00, 0x9e, 0x36, 0x64,
0xd1, 0x1e, 0x07, 0xc1, 0x00, 0x06, 0x61, 0x73,
0x73, 0x65, 0x74, 0x32, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0d, 0x6d, 0x79, 0x56, 0x61, 0x72, 0x43,
0x61, 0x70, 0x41, 0x73, 0x73, 0x65, 0x74, 0x00,
0x04, 0x4d, 0x56, 0x43, 0x41, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x3f, 0x58,
0xfd, 0xa2, 0xe9, 0xea, 0x8d, 0x9e, 0x4b, 0x18,
0x18, 0x32, 0xa0, 0x7b, 0x26, 0xda, 0xe2, 0x86,
0xf2, 0xcb, 0x64, 0x59, 0x38, 0xbb, 0x7a, 0xe2,
0x19, 0x32, 0x70, 0xe6, 0xff, 0xef, 0x00, 0x9e,
0x36, 0x64, 0xd1, 0x1e, 0x07, 0xc1, 0x00, 0x00,
0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 0x3f, 0x78, 0xe5, 0x10, 0xdf, 0x62,
0xbc, 0x48, 0xb0, 0x82, 0x9e, 0xc0, 0x6d, 0x6a,
0x6b, 0x98, 0x06, 0x2d, 0x69, 0x53, 0xc5, 0x49,
0x03, 0xde, 0x51, 0x77, 0xa1, 0x6f, 0x78, 0x11,
0x77, 0x1e, 0xf2, 0xf4, 0x65, 0x9d, 0x9e, 0x86,
0x46, 0x71, 0x00, 0x06, 0x61, 0x73, 0x73, 0x65,
0x74, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
0x6d, 0x79, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x56,
0x61, 0x72, 0x43, 0x61, 0x70, 0x41, 0x73, 0x73,
0x65, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x64, 0x59, 0x38,
0xbb, 0x7a, 0xe2, 0x19, 0x32, 0x70, 0xe6, 0xff,
0xef, 0x00, 0x9e, 0x36, 0x64, 0xd1, 0x1e, 0x07,
0xc1,
}}
cb58 := formatting.CB58{}
if err := cb58.FromString(expected); err != nil {
t.Fatal(err)
}
expectedBytes := cb58.Bytes
if result := reply.Bytes.String(); result != expected {
t.Fatalf("Create genesis returned unexpected bytes:\n\n%s\n\n%s\n\n%s",
reply.Bytes,
if result := reply.Bytes.String(); result != expected.String() {
t.Fatalf("Create genesis returned:\n%s\nExpected:\n%s",
formatting.DumpBytes{Bytes: reply.Bytes.Bytes},
formatting.DumpBytes{Bytes: expectedBytes},
formatting.DumpBytes{Bytes: expected.Bytes},
)
}
}

View File

@ -6,10 +6,12 @@ package avm
import (
"errors"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
)
var (
@ -22,16 +24,13 @@ type UnsignedTx interface {
ID() ids.ID
Bytes() []byte
NetworkID() uint32
ChainID() ids.ID
Outputs() []*TransferableOutput
Inputs() []*TransferableInput
AssetIDs() ids.Set
InputUTXOs() []*UTXOID
UTXOs() []*UTXO
InputUTXOs() []*ava.UTXOID
UTXOs() []*ava.UTXO
SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error
SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) error
SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error
ExecuteWithSideEffects(vm *VM, batch database.Batch) error
}
// Tx is the core operation that can be performed. The tx uses the UTXO model.
@ -42,12 +41,12 @@ type UnsignedTx interface {
type Tx struct {
UnsignedTx `serialize:"true"`
Creds []*Credential `serialize:"true"` // The credentials of this transaction
Creds []verify.Verifiable `serialize:"true"` // The credentials of this transaction
}
// Credentials describes the authorization that allows the Inputs to consume the
// specified UTXOs. The returned array should not be modified.
func (t *Tx) Credentials() []*Credential { return t.Creds }
func (t *Tx) Credentials() []verify.Verifiable { return t.Creds }
// SyntacticVerify verifies that this transaction is well-formed.
func (t *Tx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error {

View File

@ -8,7 +8,9 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/units"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
@ -42,21 +44,18 @@ func TestTxInvalidCredential(t *testing.T) {
c.RegisterType(&secp256k1fx.MintInput{})
c.RegisterType(&secp256k1fx.TransferInput{})
c.RegisterType(&secp256k1fx.Credential{})
c.RegisterType(&testVerifiable{})
c.RegisterType(&ava.TestVerifiable{})
tx := &Tx{
UnsignedTx: &OperationTx{BaseTx: BaseTx{
UnsignedTx: &BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
Asset: Asset{
ID: asset,
},
Asset: ava.Asset{ID: asset},
In: &secp256k1fx.TransferInput{
Amt: 20 * units.KiloAva,
Input: secp256k1fx.Input{
@ -65,14 +64,9 @@ func TestTxInvalidCredential(t *testing.T) {
},
},
},
},
},
}},
Creds: []*Credential{
&Credential{
Cred: &testVerifiable{err: errUnneededAddress},
},
},
Creds: []verify.Verifiable{&ava.TestVerifiable{Err: errUnneededAddress}},
}
b, err := c.Marshal(tx)
@ -96,21 +90,19 @@ func TestTxInvalidUnsignedTx(t *testing.T) {
c.RegisterType(&secp256k1fx.MintInput{})
c.RegisterType(&secp256k1fx.TransferInput{})
c.RegisterType(&secp256k1fx.Credential{})
c.RegisterType(&testVerifiable{})
c.RegisterType(&ava.TestVerifiable{})
tx := &Tx{
UnsignedTx: &OperationTx{BaseTx: BaseTx{
UnsignedTx: &BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
Ins: []*ava.TransferableInput{
&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
Asset: Asset{
ID: asset,
},
Asset: ava.Asset{ID: asset},
In: &secp256k1fx.TransferInput{
Amt: 20 * units.KiloAva,
Input: secp256k1fx.Input{
@ -120,14 +112,12 @@ func TestTxInvalidUnsignedTx(t *testing.T) {
},
},
},
&TransferableInput{
UTXOID: UTXOID{
&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
Asset: Asset{
ID: asset,
},
Asset: ava.Asset{ID: asset},
In: &secp256k1fx.TransferInput{
Amt: 20 * units.KiloAva,
Input: secp256k1fx.Input{
@ -138,14 +128,10 @@ func TestTxInvalidUnsignedTx(t *testing.T) {
},
},
},
}},
Creds: []*Credential{
&Credential{
Cred: &testVerifiable{},
},
&Credential{
Cred: &testVerifiable{},
},
Creds: []verify.Verifiable{
&ava.TestVerifiable{},
&ava.TestVerifiable{},
},
}
@ -170,22 +156,16 @@ func TestTxInvalidNumberOfCredentials(t *testing.T) {
c.RegisterType(&secp256k1fx.MintInput{})
c.RegisterType(&secp256k1fx.TransferInput{})
c.RegisterType(&secp256k1fx.Credential{})
c.RegisterType(&testVerifiable{})
c.RegisterType(&ava.TestVerifiable{})
tx := &Tx{
UnsignedTx: &OperationTx{
BaseTx: BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
Asset: Asset{
ID: asset,
},
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{TxID: ids.Empty, OutputIndex: 0},
Asset: ava.Asset{ID: asset},
In: &secp256k1fx.TransferInput{
Amt: 20 * units.KiloAva,
Input: secp256k1fx.Input{
@ -194,31 +174,24 @@ func TestTxInvalidNumberOfCredentials(t *testing.T) {
},
},
},
},
},
}},
},
Ops: []*Operation{
&Operation{
Asset: Asset{
ID: asset,
},
Asset: ava.Asset{ID: asset},
Ins: []*OperableInput{
&OperableInput{
UTXOID: UTXOID{
UTXOID: ava.UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
In: &testVerifiable{},
In: &ava.TestVerifiable{},
},
},
},
},
},
Creds: []*Credential{
&Credential{
Cred: &testVerifiable{},
},
},
Creds: []verify.Verifiable{&ava.TestVerifiable{}},
}
b, err := c.Marshal(tx)

View File

@ -9,10 +9,12 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
"github.com/ava-labs/gecko/vms/components/ava"
)
var (
errAssetIDMismatch = errors.New("asset IDs in the input don't match the utxo")
errWrongAssetID = errors.New("asset ID must be AVA in the atomic tx")
errMissingUTXO = errors.New("missing utxo")
errUnknownTx = errors.New("transaction is unknown")
errRejectedTx = errors.New("transaction is rejected")
@ -21,19 +23,22 @@ var (
// UniqueTx provides a de-duplication service for txs. This only provides a
// performance boost
type UniqueTx struct {
*TxState
vm *VM
txID ids.ID
t *txState
}
type txState struct {
// TxState ...
type TxState struct {
*Tx
unique, verifiedTx, verifiedState bool
validity error
tx *Tx
inputs ids.Set
inputUTXOs []*UTXOID
utxos []*UTXO
inputUTXOs []*ava.UTXOID
utxos []*ava.UTXO
deps []snowstorm.Tx
status choices.Status
@ -42,51 +47,51 @@ type txState struct {
}
func (tx *UniqueTx) refresh() {
if tx.t == nil {
tx.t = &txState{}
if tx.TxState == nil {
tx.TxState = &TxState{}
}
if tx.t.unique {
if tx.unique {
return
}
unique := tx.vm.state.UniqueTx(tx)
prevTx := tx.t.tx
prevTx := tx.Tx
if unique == tx {
// If no one was in the cache, make sure that there wasn't an
// intermediate object whose state I must reflect
if status, err := tx.vm.state.Status(tx.ID()); err == nil {
tx.t.status = status
tx.t.unique = true
tx.status = status
tx.unique = true
}
} else {
// If someone is in the cache, they must be up to date
// This ensures that every unique tx object points to the same tx state
tx.t = unique.t
tx.TxState = unique.TxState
}
if tx.t.tx != nil {
if tx.Tx != nil {
return
}
if prevTx == nil {
if innerTx, err := tx.vm.state.Tx(tx.ID()); err == nil {
tx.t.tx = innerTx
tx.Tx = innerTx
}
} else {
tx.t.tx = prevTx
tx.Tx = prevTx
}
}
// Evict is called when this UniqueTx will no longer be returned from a cache
// lookup
func (tx *UniqueTx) Evict() { tx.t.unique = false } // Lock is already held here
func (tx *UniqueTx) Evict() { tx.unique = false } // Lock is already held here
func (tx *UniqueTx) setStatus(status choices.Status) error {
tx.refresh()
if tx.t.status == status {
if tx.status == status {
return nil
}
tx.t.status = status
tx.status = status
return tx.vm.state.SetStatus(tx.ID(), status)
}
@ -95,13 +100,20 @@ func (tx *UniqueTx) ID() ids.ID { return tx.txID }
// Accept is called when the transaction was finalized as accepted by consensus
func (tx *UniqueTx) Accept() {
defer tx.vm.db.Abort()
if err := tx.setStatus(choices.Accepted); err != nil {
tx.vm.ctx.Log.Error("Failed to accept tx %s due to %s", tx.txID, err)
return
}
// Remove spent utxos
for _, utxoID := range tx.InputIDs().List() {
for _, utxo := range tx.InputUTXOs() {
if utxo.Symbolic() {
// If the UTXO is symbolic, it can't be spent
continue
}
utxoID := utxo.InputID()
if err := tx.vm.state.SpendUTXO(utxoID); err != nil {
tx.vm.ctx.Log.Error("Failed to spend utxo %s due to %s", utxoID, err)
return
@ -117,23 +129,32 @@ func (tx *UniqueTx) Accept() {
}
txID := tx.ID()
tx.vm.ctx.Log.Verbo("Accepting Tx: %s", txID)
if err := tx.vm.db.Commit(); err != nil {
tx.vm.ctx.Log.Error("Failed to commit accept %s due to %s", tx.txID, err)
commitBatch, err := tx.vm.db.CommitBatch()
if err != nil {
tx.vm.ctx.Log.Error("Failed to calculate CommitBatch for %s due to %s", txID, err)
return
}
if err := tx.ExecuteWithSideEffects(tx.vm, commitBatch); err != nil {
tx.vm.ctx.Log.Error("Failed to commit accept %s due to %s", txID, err)
return
}
tx.vm.ctx.Log.Verbo("Accepted Tx: %s", txID)
tx.vm.pubsub.Publish("accepted", txID)
tx.t.deps = nil // Needed to prevent a memory leak
tx.deps = nil // Needed to prevent a memory leak
if tx.t.onDecide != nil {
tx.t.onDecide(choices.Accepted)
if tx.onDecide != nil {
tx.onDecide(choices.Accepted)
}
}
// Reject is called when the transaction was finalized as rejected by consensus
func (tx *UniqueTx) Reject() {
defer tx.vm.db.Abort()
if err := tx.setStatus(choices.Rejected); err != nil {
tx.vm.ctx.Log.Error("Failed to reject tx %s due to %s", tx.txID, err)
return
@ -148,86 +169,89 @@ func (tx *UniqueTx) Reject() {
tx.vm.pubsub.Publish("rejected", txID)
tx.t.deps = nil // Needed to prevent a memory leak
tx.deps = nil // Needed to prevent a memory leak
if tx.t.onDecide != nil {
tx.t.onDecide(choices.Rejected)
if tx.onDecide != nil {
tx.onDecide(choices.Rejected)
}
}
// Status returns the current status of this transaction
func (tx *UniqueTx) Status() choices.Status {
tx.refresh()
return tx.t.status
return tx.status
}
// Dependencies returns the set of transactions this transaction builds on
func (tx *UniqueTx) Dependencies() []snowstorm.Tx {
tx.refresh()
if tx.t.tx == nil || len(tx.t.deps) != 0 {
return tx.t.deps
if tx.Tx == nil || len(tx.deps) != 0 {
return tx.deps
}
txIDs := ids.Set{}
for _, in := range tx.InputUTXOs() {
if in.Symbolic() {
continue
}
txID, _ := in.InputSource()
if !txIDs.Contains(txID) {
txIDs.Add(txID)
tx.t.deps = append(tx.t.deps, &UniqueTx{
tx.deps = append(tx.deps, &UniqueTx{
vm: tx.vm,
txID: txID,
})
}
}
for _, assetID := range tx.t.tx.AssetIDs().List() {
for _, assetID := range tx.Tx.AssetIDs().List() {
if !txIDs.Contains(assetID) {
txIDs.Add(assetID)
tx.t.deps = append(tx.t.deps, &UniqueTx{
tx.deps = append(tx.deps, &UniqueTx{
vm: tx.vm,
txID: assetID,
})
}
}
return tx.t.deps
return tx.deps
}
// InputIDs returns the set of utxoIDs this transaction consumes
func (tx *UniqueTx) InputIDs() ids.Set {
tx.refresh()
if tx.t.tx == nil || tx.t.inputs.Len() != 0 {
return tx.t.inputs
if tx.Tx == nil || tx.inputs.Len() != 0 {
return tx.inputs
}
for _, utxo := range tx.InputUTXOs() {
tx.t.inputs.Add(utxo.InputID())
tx.inputs.Add(utxo.InputID())
}
return tx.t.inputs
return tx.inputs
}
// InputUTXOs returns the utxos that will be consumed on tx acceptance
func (tx *UniqueTx) InputUTXOs() []*UTXOID {
func (tx *UniqueTx) InputUTXOs() []*ava.UTXOID {
tx.refresh()
if tx.t.tx == nil || len(tx.t.inputUTXOs) != 0 {
return tx.t.inputUTXOs
if tx.Tx == nil || len(tx.inputUTXOs) != 0 {
return tx.inputUTXOs
}
tx.t.inputUTXOs = tx.t.tx.InputUTXOs()
return tx.t.inputUTXOs
tx.inputUTXOs = tx.Tx.InputUTXOs()
return tx.inputUTXOs
}
// UTXOs returns the utxos that will be added to the UTXO set on tx acceptance
func (tx *UniqueTx) UTXOs() []*UTXO {
func (tx *UniqueTx) UTXOs() []*ava.UTXO {
tx.refresh()
if tx.t.tx == nil || len(tx.t.utxos) != 0 {
return tx.t.utxos
if tx.Tx == nil || len(tx.utxos) != 0 {
return tx.utxos
}
tx.t.utxos = tx.t.tx.UTXOs()
return tx.t.utxos
tx.utxos = tx.Tx.UTXOs()
return tx.utxos
}
// Bytes returns the binary representation of this transaction
func (tx *UniqueTx) Bytes() []byte {
tx.refresh()
return tx.t.tx.Bytes()
return tx.Tx.Bytes()
}
// Verify the validity of this transaction
@ -248,39 +272,40 @@ func (tx *UniqueTx) Verify() error {
func (tx *UniqueTx) SyntacticVerify() error {
tx.refresh()
if tx.t.tx == nil {
if tx.Tx == nil {
return errUnknownTx
}
if tx.t.verifiedTx {
return tx.t.validity
if tx.verifiedTx {
return tx.validity
}
tx.t.verifiedTx = true
tx.t.validity = tx.t.tx.SyntacticVerify(tx.vm.ctx, tx.vm.codec, len(tx.vm.fxs))
return tx.t.validity
tx.verifiedTx = true
tx.validity = tx.Tx.SyntacticVerify(tx.vm.ctx, tx.vm.codec, len(tx.vm.fxs))
return tx.validity
}
// SemanticVerify the validity of this transaction
func (tx *UniqueTx) SemanticVerify() error {
tx.SyntacticVerify()
if tx.t.validity != nil || tx.t.verifiedState {
return tx.t.validity
if tx.validity != nil || tx.verifiedState {
return tx.validity
}
tx.t.verifiedState = true
tx.t.validity = tx.t.tx.SemanticVerify(tx.vm, tx)
err := tx.Tx.SemanticVerify(tx.vm, tx)
if err != nil {
return err
}
if tx.t.validity == nil {
tx.verifiedState = true
tx.vm.pubsub.Publish("verified", tx.ID())
}
return tx.t.validity
return nil
}
// UnsignedBytes returns the unsigned bytes of the transaction
func (tx *UniqueTx) UnsignedBytes() []byte {
b, err := tx.vm.codec.Marshal(&tx.t.tx.UnsignedTx)
b, err := tx.vm.codec.Marshal(&tx.UnsignedTx)
tx.vm.ctx.Log.AssertNoError(err)
return b
}

View File

@ -3,20 +3,10 @@
package avm
type testVerifiable struct{ err error }
func (v *testVerifiable) Verify() error { return v.err }
type TestTransferable struct {
testVerifiable
Val uint64 `serialize:"true"`
}
func (t *TestTransferable) Amount() uint64 { return t.Val }
import "github.com/ava-labs/gecko/vms/components/ava"
type testAddressable struct {
TestTransferable `serialize:"true"`
ava.TestTransferable `serialize:"true"`
Addrs [][]byte `serialize:"true"`
}

View File

@ -23,6 +23,7 @@ import (
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/timer"
"github.com/ava-labs/gecko/utils/wrappers"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
cjson "github.com/ava-labs/gecko/utils/json"
@ -49,6 +50,9 @@ var (
type VM struct {
ids.Aliaser
ava ids.ID
platform ids.ID
// Contains information of where this VM is executing
ctx *snow.Context
@ -122,24 +126,12 @@ func (vm *VM) Initialize(
return errs.Err
}
vm.state = &prefixedState{
state: &state{
c: &cache.LRU{Size: stateCacheSize},
vm: vm,
},
tx: &cache.LRU{Size: idCacheSize},
utxo: &cache.LRU{Size: idCacheSize},
txStatus: &cache.LRU{Size: idCacheSize},
funds: &cache.LRU{Size: idCacheSize},
uniqueTx: &cache.EvictableLRU{Size: txCacheSize},
}
c := codec.NewDefault()
c.RegisterType(&BaseTx{})
c.RegisterType(&CreateAssetTx{})
c.RegisterType(&OperationTx{})
c.RegisterType(&ImportTx{})
c.RegisterType(&ExportTx{})
vm.fxs = make([]*parsedFx, len(fxs))
for i, fxContainer := range fxs {
@ -166,6 +158,21 @@ func (vm *VM) Initialize(
vm.codec = c
vm.state = &prefixedState{
state: &state{State: ava.State{
Cache: &cache.LRU{Size: stateCacheSize},
DB: vm.db,
Codec: vm.codec,
}},
tx: &cache.LRU{Size: idCacheSize},
utxo: &cache.LRU{Size: idCacheSize},
txStatus: &cache.LRU{Size: idCacheSize},
funds: &cache.LRU{Size: idCacheSize},
uniqueTx: &cache.EvictableLRU{Size: txCacheSize},
}
if err := vm.initAliases(genesisBytes); err != nil {
return err
}
@ -264,20 +271,45 @@ func (vm *VM) IssueTx(b []byte, onDecide func(choices.Status)) (ids.ID, error) {
return ids.ID{}, err
}
vm.issueTx(tx)
tx.t.onDecide = onDecide
tx.onDecide = onDecide
return tx.ID(), nil
}
// GetAtomicUTXOs returns the utxos that at least one of the provided addresses is
// referenced in.
func (vm *VM) GetAtomicUTXOs(addrs ids.Set) ([]*ava.UTXO, error) {
smDB := vm.ctx.SharedMemory.GetDatabase(vm.platform)
defer vm.ctx.SharedMemory.ReleaseDatabase(vm.platform)
state := ava.NewPrefixedState(smDB, vm.codec)
utxoIDs := ids.Set{}
for _, addr := range addrs.List() {
utxos, _ := state.PlatformFunds(addr)
utxoIDs.Add(utxos...)
}
utxos := []*ava.UTXO{}
for _, utxoID := range utxoIDs.List() {
utxo, err := state.PlatformUTXO(utxoID)
if err != nil {
return nil, err
}
utxos = append(utxos, utxo)
}
return utxos, nil
}
// GetUTXOs returns the utxos that at least one of the provided addresses is
// referenced in.
func (vm *VM) GetUTXOs(addrs ids.Set) ([]*UTXO, error) {
func (vm *VM) GetUTXOs(addrs ids.Set) ([]*ava.UTXO, error) {
utxoIDs := ids.Set{}
for _, addr := range addrs.List() {
utxos, _ := vm.state.Funds(addr)
utxoIDs.Add(utxos...)
}
utxos := []*UTXO{}
utxos := []*ava.UTXO{}
for _, utxoID := range utxoIDs.List() {
utxo, err := vm.state.UTXO(utxoID)
if err != nil {
@ -402,18 +434,18 @@ func (vm *VM) parseTx(b []byte) (*UniqueTx, error) {
rawTx.Initialize(b)
tx := &UniqueTx{
TxState: &TxState{
Tx: rawTx,
},
vm: vm,
txID: rawTx.ID(),
t: &txState{
tx: rawTx,
},
}
if err := tx.SyntacticVerify(); err != nil {
return nil, err
}
if tx.Status() == choices.Unknown {
if err := vm.state.SetTx(tx.ID(), tx.t.tx); err != nil {
if err := vm.state.SetTx(tx.ID(), tx.Tx); err != nil {
return nil, err
}
tx.setStatus(choices.Processing)
@ -432,6 +464,32 @@ func (vm *VM) issueTx(tx snowstorm.Tx) {
}
}
func (vm *VM) getUTXO(utxoID *ava.UTXOID) (*ava.UTXO, error) {
inputID := utxoID.InputID()
utxo, err := vm.state.UTXO(inputID)
if err == nil {
return utxo, nil
}
inputTx, inputIndex := utxoID.InputSource()
parent := UniqueTx{
vm: vm,
txID: inputTx,
}
if err := parent.Verify(); err != nil {
return nil, errMissingUTXO
} else if status := parent.Status(); status.Decided() {
return nil, errMissingUTXO
}
parentUTXOs := parent.UTXOs()
if uint32(len(parentUTXOs)) <= inputIndex || int(inputIndex) < 0 {
return nil, errInvalidUTXO
}
return parentUTXOs[int(inputIndex)], nil
}
func (vm *VM) getFx(val interface{}) (int, error) {
valType := reflect.TypeOf(val)
fx, exists := vm.typeToFxIndex[valType]
@ -449,7 +507,7 @@ func (vm *VM) verifyFxUsage(fxID int, assetID ids.ID) bool {
if status := tx.Status(); !status.Fetched() {
return false
}
createAssetTx, ok := tx.t.tx.UnsignedTx.(*CreateAssetTx)
createAssetTx, ok := tx.UnsignedTx.(*CreateAssetTx)
if !ok {
return false
}

View File

@ -15,7 +15,9 @@ import (
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/units"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
@ -50,6 +52,8 @@ func GetFirstTxFromGenesisTest(genesisBytes []byte, t *testing.T) *Tx {
c.RegisterType(&BaseTx{})
c.RegisterType(&CreateAssetTx{})
c.RegisterType(&OperationTx{})
c.RegisterType(&ImportTx{})
c.RegisterType(&ExportTx{})
c.RegisterType(&secp256k1fx.MintOutput{})
c.RegisterType(&secp256k1fx.TransferOutput{})
c.RegisterType(&secp256k1fx.MintInput{})
@ -205,7 +209,7 @@ func TestTxSerialization(t *testing.T) {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// fxID:
0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x06,
// secp256k1 Transferable Output:
// amount:
0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00,
@ -226,7 +230,7 @@ func TestTxSerialization(t *testing.T) {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// fxID:
0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x06,
// secp256k1 Transferable Output:
// amount:
0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00,
@ -247,7 +251,7 @@ func TestTxSerialization(t *testing.T) {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// fxID:
0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x06,
// secp256k1 Transferable Output:
// amount:
0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00,
@ -276,7 +280,7 @@ func TestTxSerialization(t *testing.T) {
// number of outputs:
0x00, 0x00, 0x00, 0x01,
// fxID:
0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x05,
// secp256k1 Mint Output:
// threshold:
0x00, 0x00, 0x00, 0x01,
@ -297,12 +301,9 @@ func TestTxSerialization(t *testing.T) {
},
Ops: []*Operation{
&Operation{
Asset: Asset{
ID: asset,
},
Outs: []*OperableOutput{
&OperableOutput{
Out: &secp256k1fx.MintOutput{
Asset: ava.Asset{ID: asset},
Outs: []verify.Verifiable{
&secp256k1fx.MintOutput{
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{keys[0].PublicKey().Address()},
@ -311,16 +312,13 @@ func TestTxSerialization(t *testing.T) {
},
},
},
},
}
tx := &Tx{UnsignedTx: unsignedTx}
for _, key := range keys {
addr := key.PublicKey().Address()
unsignedTx.Outs = append(unsignedTx.Outs, &TransferableOutput{
Asset: Asset{
ID: asset,
},
unsignedTx.Outs = append(unsignedTx.Outs, &ava.TransferableOutput{
Asset: ava.Asset{ID: asset},
Out: &secp256k1fx.TransferOutput{
Amt: 20 * units.KiloAva,
OutputOwners: secp256k1fx.OutputOwners{
@ -335,6 +333,8 @@ func TestTxSerialization(t *testing.T) {
c.RegisterType(&BaseTx{})
c.RegisterType(&CreateAssetTx{})
c.RegisterType(&OperationTx{})
c.RegisterType(&ImportTx{})
c.RegisterType(&ExportTx{})
c.RegisterType(&secp256k1fx.MintOutput{})
c.RegisterType(&secp256k1fx.TransferOutput{})
c.RegisterType(&secp256k1fx.MintInput{})
@ -441,18 +441,15 @@ func TestIssueTx(t *testing.T) {
genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t)
newTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{
newTx := &Tx{UnsignedTx: &BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: genesisTx.ID(),
OutputIndex: 1,
},
Asset: Asset{
ID: genesisTx.ID(),
},
Asset: ava.Asset{ID: genesisTx.ID()},
In: &secp256k1fx.TransferInput{
Amt: 50000,
Input: secp256k1fx.Input{
@ -461,9 +458,8 @@ func TestIssueTx(t *testing.T) {
},
},
},
},
},
}}}
}},
}}
unsignedBytes, err := vm.codec.Marshal(&newTx.UnsignedTx)
if err != nil {
@ -478,12 +474,10 @@ func TestIssueTx(t *testing.T) {
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
newTx.Creds = append(newTx.Creds, &Credential{
Cred: &secp256k1fx.Credential{
newTx.Creds = append(newTx.Creds, &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
},
})
b, err := vm.codec.Marshal(newTx)
@ -576,16 +570,15 @@ func TestIssueDependentTx(t *testing.T) {
key := keys[0]
firstTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{
firstTx := &Tx{UnsignedTx: &BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: genesisTx.ID(),
OutputIndex: 1,
},
Asset: Asset{ID: genesisTx.ID()},
Asset: ava.Asset{ID: genesisTx.ID()},
In: &secp256k1fx.TransferInput{
Amt: 50000,
Input: secp256k1fx.Input{
@ -594,11 +587,9 @@ func TestIssueDependentTx(t *testing.T) {
},
},
},
},
},
Outs: []*TransferableOutput{
&TransferableOutput{
Asset: Asset{ID: genesisTx.ID()},
}},
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{ID: genesisTx.ID()},
Out: &secp256k1fx.TransferOutput{
Amt: 50000,
OutputOwners: secp256k1fx.OutputOwners{
@ -606,9 +597,8 @@ func TestIssueDependentTx(t *testing.T) {
Addrs: []ids.ShortID{key.PublicKey().Address()},
},
},
},
},
}}}
}},
}}
unsignedBytes, err := vm.codec.Marshal(&firstTx.UnsignedTx)
if err != nil {
@ -622,12 +612,10 @@ func TestIssueDependentTx(t *testing.T) {
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
firstTx.Creds = append(firstTx.Creds, &Credential{
Cred: &secp256k1fx.Credential{
firstTx.Creds = append(firstTx.Creds, &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
},
})
b, err := vm.codec.Marshal(firstTx)
@ -641,16 +629,15 @@ func TestIssueDependentTx(t *testing.T) {
t.Fatal(err)
}
secondTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{
secondTx := &Tx{UnsignedTx: &BaseTx{
NetID: networkID,
BCID: chainID,
Ins: []*TransferableInput{
&TransferableInput{
UTXOID: UTXOID{
Ins: []*ava.TransferableInput{&ava.TransferableInput{
UTXOID: ava.UTXOID{
TxID: firstTx.ID(),
OutputIndex: 0,
},
Asset: Asset{ID: genesisTx.ID()},
Asset: ava.Asset{ID: genesisTx.ID()},
In: &secp256k1fx.TransferInput{
Amt: 50000,
Input: secp256k1fx.Input{
@ -659,9 +646,8 @@ func TestIssueDependentTx(t *testing.T) {
},
},
},
},
},
}}}
}},
}}
unsignedBytes, err = vm.codec.Marshal(&secondTx.UnsignedTx)
if err != nil {
@ -675,12 +661,10 @@ func TestIssueDependentTx(t *testing.T) {
fixedSig = [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
secondTx.Creds = append(secondTx.Creds, &Credential{
Cred: &secp256k1fx.Credential{
secondTx.Creds = append(secondTx.Creds, &secp256k1fx.Credential{
Sigs: [][crypto.SECP256K1RSigLen]byte{
fixedSig,
},
},
})
b, err = vm.codec.Marshal(secondTx)

View File

@ -1,7 +1,7 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"errors"

View File

@ -1,7 +1,7 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"testing"

View File

@ -0,0 +1,57 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ava
import (
"errors"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/math"
"github.com/ava-labs/gecko/utils/wrappers"
)
var (
errInsufficientFunds = errors.New("insufficient funds")
)
// FlowChecker ...
type FlowChecker struct {
consumed, produced map[[32]byte]uint64
errs wrappers.Errs
}
// NewFlowChecker ...
func NewFlowChecker() *FlowChecker {
return &FlowChecker{
consumed: make(map[[32]byte]uint64),
produced: make(map[[32]byte]uint64),
}
}
// Consume ...
func (fc *FlowChecker) Consume(assetID ids.ID, amount uint64) { fc.add(fc.consumed, assetID, amount) }
// Produce ...
func (fc *FlowChecker) Produce(assetID ids.ID, amount uint64) { fc.add(fc.produced, assetID, amount) }
func (fc *FlowChecker) add(value map[[32]byte]uint64, assetID ids.ID, amount uint64) {
var err error
assetIDKey := assetID.Key()
value[assetIDKey], err = math.Add64(value[assetIDKey], amount)
fc.errs.Add(err)
}
// Verify ...
func (fc *FlowChecker) Verify() error {
if !fc.errs.Errored() {
for assetID, producedAssetAmount := range fc.produced {
consumedAssetAmount := fc.consumed[assetID]
if producedAssetAmount > consumedAssetAmount {
fc.errs.Add(errInsufficientFunds)
break
}
}
}
return fc.errs.Err
}

View File

@ -1,7 +1,7 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"errors"
@ -15,25 +15,26 @@ var (
errMetadataNotInitialize = errors.New("metadata was never initialized and is not valid")
)
type metadata struct {
// Metadata ...
type Metadata struct {
id ids.ID // The ID of this data
bytes []byte // Byte representation of this data
}
// Bytes returns the binary representation of this data
func (md *metadata) Initialize(bytes []byte) {
// Initialize set the bytes and ID
func (md *Metadata) Initialize(bytes []byte) {
md.id = ids.NewID(hashing.ComputeHash256Array(bytes))
md.bytes = bytes
}
// ID returns the unique ID of this data
func (md *metadata) ID() ids.ID { return md.id }
func (md *Metadata) ID() ids.ID { return md.id }
// Bytes returns the binary representation of this data
func (md *metadata) Bytes() []byte { return md.bytes }
func (md *Metadata) Bytes() []byte { return md.bytes }
// Verify implements the verify.Verifiable interface
func (md *metadata) Verify() error {
func (md *Metadata) Verify() error {
switch {
case md == nil:
return errNilMetadata

View File

@ -1,21 +1,21 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"testing"
)
func TestMetaDataVerifyNil(t *testing.T) {
md := (*metadata)(nil)
md := (*Metadata)(nil)
if err := md.Verify(); err == nil {
t.Fatalf("Should have errored due to nil metadata")
}
}
func TestMetaDataVerifyUninitialized(t *testing.T) {
md := &metadata{}
md := &Metadata{}
if err := md.Verify(); err == nil {
t.Fatalf("Should have errored due to uninitialized metadata")
}

View File

@ -0,0 +1,208 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ava
import (
"github.com/ava-labs/gecko/cache"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/vms/components/codec"
)
// Addressable is the interface a feature extension must provide to be able to
// be tracked as a part of the utxo set for a set of addresses
type Addressable interface {
Addresses() [][]byte
}
const (
platformUTXOID uint64 = iota
platformStatusID
platformFundsID
avmUTXOID
avmStatusID
avmFundsID
)
const (
stateCacheSize = 10000
idCacheSize = 10000
)
type chainState struct {
*State
utxoIDPrefix, statusIDPrefix, fundsIDPrefix uint64
utxoID, statusID, fundsID cache.Cacher
}
// UTXO attempts to load a utxo from platform's storage.
func (s *chainState) UTXO(id ids.ID) (*UTXO, error) {
return s.State.UTXO(UniqueID(id, s.utxoIDPrefix, s.utxoID))
}
// Funds returns the mapping from the 32 byte representation of an
// address to a list of utxo IDs that reference the address.
func (s *chainState) Funds(id ids.ID) ([]ids.ID, error) {
return s.IDs(UniqueID(id, s.fundsIDPrefix, s.fundsID))
}
// SpendUTXO consumes the provided platform utxo.
func (s *chainState) SpendUTXO(utxoID ids.ID) error {
utxo, err := s.UTXO(utxoID)
if err != nil {
return s.setStatus(utxoID, choices.Accepted)
} else if err := s.setUTXO(utxoID, nil); err != nil {
return err
}
if addressable, ok := utxo.Out.(Addressable); ok {
return s.removeUTXO(addressable.Addresses(), utxoID)
}
return nil
}
// FundUTXO adds the provided utxo to the database
func (s *chainState) FundUTXO(utxo *UTXO) error {
utxoID := utxo.InputID()
if _, err := s.status(utxoID); err == nil {
return s.setStatus(utxoID, choices.Unknown)
} else if err := s.setUTXO(utxoID, utxo); err != nil {
return err
}
if addressable, ok := utxo.Out.(Addressable); ok {
return s.addUTXO(addressable.Addresses(), utxoID)
}
return nil
}
// setUTXO saves the provided utxo to platform's storage.
func (s *chainState) setUTXO(id ids.ID, utxo *UTXO) error {
return s.SetUTXO(UniqueID(id, s.utxoIDPrefix, s.utxoID), utxo)
}
func (s *chainState) status(id ids.ID) (choices.Status, error) {
return s.Status(UniqueID(id, s.statusIDPrefix, s.statusID))
}
// setStatus saves the provided platform status to storage.
func (s *chainState) setStatus(id ids.ID, status choices.Status) error {
return s.State.SetStatus(UniqueID(id, s.statusIDPrefix, s.statusID), status)
}
func (s *chainState) removeUTXO(addrs [][]byte, utxoID ids.ID) error {
for _, addr := range addrs {
addrID := ids.NewID(hashing.ComputeHash256Array(addr))
utxos := ids.Set{}
funds, _ := s.Funds(addrID)
utxos.Add(funds...)
utxos.Remove(utxoID)
if err := s.setFunds(addrID, utxos.List()); err != nil {
return err
}
}
return nil
}
func (s *chainState) addUTXO(addrs [][]byte, utxoID ids.ID) error {
for _, addr := range addrs {
addrID := ids.NewID(hashing.ComputeHash256Array(addr))
utxos := ids.Set{}
funds, _ := s.Funds(addrID)
utxos.Add(funds...)
utxos.Add(utxoID)
if err := s.setFunds(addrID, utxos.List()); err != nil {
return err
}
}
return nil
}
func (s *chainState) setFunds(id ids.ID, idSlice []ids.ID) error {
return s.SetIDs(UniqueID(id, s.fundsIDPrefix, s.fundsID), idSlice)
}
// PrefixedState wraps a state object. By prefixing the state, there will
// be no collisions between different types of objects that have the same hash.
type PrefixedState struct {
platform, avm chainState
}
// NewPrefixedState ...
func NewPrefixedState(db database.Database, codec codec.Codec) *PrefixedState {
state := &State{
Cache: &cache.LRU{Size: stateCacheSize},
DB: db,
Codec: codec,
}
return &PrefixedState{
platform: chainState{
State: state,
utxoIDPrefix: platformUTXOID,
statusIDPrefix: platformStatusID,
fundsIDPrefix: platformFundsID,
utxoID: &cache.LRU{Size: idCacheSize},
statusID: &cache.LRU{Size: idCacheSize},
fundsID: &cache.LRU{Size: idCacheSize},
},
avm: chainState{
State: state,
utxoIDPrefix: avmUTXOID,
statusIDPrefix: avmStatusID,
fundsIDPrefix: avmFundsID,
utxoID: &cache.LRU{Size: idCacheSize},
statusID: &cache.LRU{Size: idCacheSize},
fundsID: &cache.LRU{Size: idCacheSize},
},
}
}
// PlatformUTXO attempts to load a utxo from platform's storage.
func (s *PrefixedState) PlatformUTXO(id ids.ID) (*UTXO, error) {
return s.platform.UTXO(id)
}
// PlatformFunds returns the mapping from the 32 byte representation of an
// address to a list of utxo IDs that reference the address.
func (s *PrefixedState) PlatformFunds(id ids.ID) ([]ids.ID, error) {
return s.platform.Funds(id)
}
// SpendPlatformUTXO consumes the provided platform utxo.
func (s *PrefixedState) SpendPlatformUTXO(utxoID ids.ID) error {
return s.platform.SpendUTXO(utxoID)
}
// FundPlatformUTXO adds the provided utxo to the database
func (s *PrefixedState) FundPlatformUTXO(utxo *UTXO) error {
return s.platform.FundUTXO(utxo)
}
// AVMUTXO attempts to load a utxo from avm's storage.
func (s *PrefixedState) AVMUTXO(id ids.ID) (*UTXO, error) {
return s.avm.UTXO(id)
}
// AVMFunds returns the mapping from the 32 byte representation of an
// address to a list of utxo IDs that reference the address.
func (s *PrefixedState) AVMFunds(id ids.ID) ([]ids.ID, error) {
return s.avm.Funds(id)
}
// SpendAVMUTXO consumes the provided platform utxo.
func (s *PrefixedState) SpendAVMUTXO(utxoID ids.ID) error {
return s.avm.SpendUTXO(utxoID)
}
// FundAVMUTXO adds the provided utxo to the database
func (s *PrefixedState) FundAVMUTXO(utxo *UTXO) error {
return s.avm.FundUTXO(utxo)
}

153
vms/components/ava/state.go Normal file
View File

@ -0,0 +1,153 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ava
import (
"errors"
"github.com/ava-labs/gecko/cache"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/vms/components/codec"
)
var (
errCacheTypeMismatch = errors.New("type returned from cache doesn't match the expected type")
)
// UniqueID returns a unique identifier
func UniqueID(id ids.ID, prefix uint64, cacher cache.Cacher) ids.ID {
if cachedIDIntf, found := cacher.Get(id); found {
return cachedIDIntf.(ids.ID)
}
uID := id.Prefix(prefix)
cacher.Put(id, uID)
return uID
}
// State is a thin wrapper around a database to provide, caching, serialization,
// and de-serialization.
type State struct {
Cache cache.Cacher
DB database.Database
Codec codec.Codec
}
// UTXO attempts to load a utxo from storage.
func (s *State) UTXO(id ids.ID) (*UTXO, error) {
if utxoIntf, found := s.Cache.Get(id); found {
if utxo, ok := utxoIntf.(*UTXO); ok {
return utxo, nil
}
return nil, errCacheTypeMismatch
}
bytes, err := s.DB.Get(id.Bytes())
if err != nil {
return nil, err
}
// The key was in the database
utxo := &UTXO{}
if err := s.Codec.Unmarshal(bytes, utxo); err != nil {
return nil, err
}
s.Cache.Put(id, utxo)
return utxo, nil
}
// SetUTXO saves the provided utxo to storage.
func (s *State) SetUTXO(id ids.ID, utxo *UTXO) error {
if utxo == nil {
s.Cache.Evict(id)
return s.DB.Delete(id.Bytes())
}
bytes, err := s.Codec.Marshal(utxo)
if err != nil {
return err
}
s.Cache.Put(id, utxo)
return s.DB.Put(id.Bytes(), bytes)
}
// Status returns a status from storage.
func (s *State) Status(id ids.ID) (choices.Status, error) {
if statusIntf, found := s.Cache.Get(id); found {
if status, ok := statusIntf.(choices.Status); ok {
return status, nil
}
return choices.Unknown, errCacheTypeMismatch
}
bytes, err := s.DB.Get(id.Bytes())
if err != nil {
return choices.Unknown, err
}
var status choices.Status
s.Codec.Unmarshal(bytes, &status)
s.Cache.Put(id, status)
return status, nil
}
// SetStatus saves a status in storage.
func (s *State) SetStatus(id ids.ID, status choices.Status) error {
if status == choices.Unknown {
s.Cache.Evict(id)
return s.DB.Delete(id.Bytes())
}
s.Cache.Put(id, status)
bytes, err := s.Codec.Marshal(status)
if err != nil {
return err
}
return s.DB.Put(id.Bytes(), bytes)
}
// IDs returns a slice of IDs from storage
func (s *State) IDs(id ids.ID) ([]ids.ID, error) {
if idsIntf, found := s.Cache.Get(id); found {
if idSlice, ok := idsIntf.([]ids.ID); ok {
return idSlice, nil
}
return nil, errCacheTypeMismatch
}
bytes, err := s.DB.Get(id.Bytes())
if err != nil {
return nil, err
}
idSlice := []ids.ID(nil)
if err := s.Codec.Unmarshal(bytes, &idSlice); err != nil {
return nil, err
}
s.Cache.Put(id, idSlice)
return idSlice, nil
}
// SetIDs saves a slice of IDs to the database.
func (s *State) SetIDs(id ids.ID, idSlice []ids.ID) error {
if len(idSlice) == 0 {
s.Cache.Evict(id)
return s.DB.Delete(id.Bytes())
}
s.Cache.Put(id, idSlice)
bytes, err := s.Codec.Marshal(idSlice)
if err != nil {
return err
}
return s.DB.Put(id.Bytes(), bytes)
}

View File

@ -0,0 +1,20 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ava
// TestVerifiable ...
type TestVerifiable struct{ Err error }
// Verify ...
func (v *TestVerifiable) Verify() error { return v.Err }
// TestTransferable ...
type TestTransferable struct {
TestVerifiable
Val uint64 `serialize:"true"`
}
// Amount ...
func (t *TestTransferable) Amount() uint64 { return t.Val }

View File

@ -1,7 +1,7 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"bytes"
@ -9,6 +9,7 @@ import (
"sort"
"github.com/ava-labs/gecko/utils"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
)
@ -21,15 +22,25 @@ var (
errNilTransferableFxInput = errors.New("nil transferable feature extension input is not valid")
)
// Transferable is the interface a feature extension must provide to transfer
// value between features extensions.
type Transferable interface {
verify.Verifiable
// Amount returns how much value this output consumes of the asset in its
// transaction.
Amount() uint64
}
// TransferableOutput ...
type TransferableOutput struct {
Asset `serialize:"true"`
Out FxTransferable `serialize:"true"`
Out Transferable `serialize:"true"`
}
// Output returns the feature extension output that this Output is using.
func (out *TransferableOutput) Output() FxTransferable { return out.Out }
func (out *TransferableOutput) Output() Transferable { return out.Out }
// Verify implements the verify.Verifiable interface
func (out *TransferableOutput) Verify() error {
@ -90,11 +101,11 @@ type TransferableInput struct {
UTXOID `serialize:"true"`
Asset `serialize:"true"`
In FxTransferable `serialize:"true"`
In Transferable `serialize:"true"`
}
// Input returns the feature extension input that this Input is using.
func (in *TransferableInput) Input() FxTransferable { return in.In }
func (in *TransferableInput) Input() Transferable { return in.In }
// Verify implements the verify.Verifiable interface
func (in *TransferableInput) Verify() error {
@ -126,7 +137,46 @@ func (ins innerSortTransferableInputs) Less(i, j int) bool {
func (ins innerSortTransferableInputs) Len() int { return len(ins) }
func (ins innerSortTransferableInputs) Swap(i, j int) { ins[j], ins[i] = ins[i], ins[j] }
func sortTransferableInputs(ins []*TransferableInput) { sort.Sort(innerSortTransferableInputs(ins)) }
func isSortedAndUniqueTransferableInputs(ins []*TransferableInput) bool {
// SortTransferableInputs ...
func SortTransferableInputs(ins []*TransferableInput) { sort.Sort(innerSortTransferableInputs(ins)) }
// IsSortedAndUniqueTransferableInputs ...
func IsSortedAndUniqueTransferableInputs(ins []*TransferableInput) bool {
return utils.IsSortedAndUnique(innerSortTransferableInputs(ins))
}
type innerSortTransferableInputsWithSigners struct {
ins []*TransferableInput
signers [][]*crypto.PrivateKeySECP256K1R
}
func (ins *innerSortTransferableInputsWithSigners) Less(i, j int) bool {
iID, iIndex := ins.ins[i].InputSource()
jID, jIndex := ins.ins[j].InputSource()
switch bytes.Compare(iID.Bytes(), jID.Bytes()) {
case -1:
return true
case 0:
return iIndex < jIndex
default:
return false
}
}
func (ins *innerSortTransferableInputsWithSigners) Len() int { return len(ins.ins) }
func (ins *innerSortTransferableInputsWithSigners) Swap(i, j int) {
ins.ins[j], ins.ins[i] = ins.ins[i], ins.ins[j]
ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j]
}
// SortTransferableInputsWithSigners sorts the inputs and signers based on the
// input's utxo ID
func SortTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) {
sort.Sort(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
}
// IsSortedAndUniqueTransferableInputsWithSigners returns true if the inputs are
// sorted and unique
func IsSortedAndUniqueTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) bool {
return utils.IsSortedAndUnique(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
}

View File

@ -1,7 +1,7 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"bytes"
@ -21,11 +21,7 @@ func TestTransferableOutputVerifyNil(t *testing.T) {
}
func TestTransferableOutputVerifyNilFx(t *testing.T) {
to := &TransferableOutput{
Asset: Asset{
ID: ids.Empty,
},
}
to := &TransferableOutput{Asset: Asset{ID: ids.Empty}}
if err := to.Verify(); err == nil {
t.Fatalf("Should have errored due to nil transferable fx output")
}
@ -33,12 +29,8 @@ func TestTransferableOutputVerifyNilFx(t *testing.T) {
func TestTransferableOutputVerify(t *testing.T) {
to := &TransferableOutput{
Asset: Asset{
ID: ids.Empty,
},
Out: &TestTransferable{
Val: 1,
},
Asset: Asset{ID: ids.Empty},
Out: &TestTransferable{Val: 1},
}
if err := to.Verify(); err != nil {
t.Fatal(err)
@ -54,33 +46,23 @@ func TestTransferableOutputSorting(t *testing.T) {
outs := []*TransferableOutput{
&TransferableOutput{
Asset: Asset{
ID: ids.NewID([32]byte{1}),
},
Asset: Asset{ID: ids.NewID([32]byte{1})},
Out: &TestTransferable{Val: 1},
},
&TransferableOutput{
Asset: Asset{
ID: ids.Empty,
},
Asset: Asset{ID: ids.Empty},
Out: &TestTransferable{Val: 1},
},
&TransferableOutput{
Asset: Asset{
ID: ids.NewID([32]byte{1}),
},
Asset: Asset{ID: ids.NewID([32]byte{1})},
Out: &TestTransferable{Val: 0},
},
&TransferableOutput{
Asset: Asset{
ID: ids.Empty,
},
Asset: Asset{ID: ids.Empty},
Out: &TestTransferable{Val: 0},
},
&TransferableOutput{
Asset: Asset{
ID: ids.Empty,
},
Asset: Asset{ID: ids.Empty},
Out: &TestTransferable{Val: 0},
},
}
@ -243,11 +225,11 @@ func TestTransferableInputSorting(t *testing.T) {
},
}
if isSortedAndUniqueTransferableInputs(ins) {
if IsSortedAndUniqueTransferableInputs(ins) {
t.Fatalf("Shouldn't be sorted")
}
sortTransferableInputs(ins)
if !isSortedAndUniqueTransferableInputs(ins) {
SortTransferableInputs(ins)
if !IsSortedAndUniqueTransferableInputs(ins) {
t.Fatalf("Should be sorted")
}
@ -260,7 +242,7 @@ func TestTransferableInputSorting(t *testing.T) {
In: &TestTransferable{},
})
if isSortedAndUniqueTransferableInputs(ins) {
if IsSortedAndUniqueTransferableInputs(ins) {
t.Fatalf("Shouldn't be unique")
}
}

View File

@ -1,7 +1,7 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"errors"

View File

@ -1,7 +1,7 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"errors"
@ -20,7 +20,9 @@ type UTXOID struct {
TxID ids.ID `serialize:"true"`
OutputIndex uint32 `serialize:"true"`
// Cached:
// Symbol is false if the UTXO should be part of the DB
Symbol bool
// id is the unique ID of a UTXO, it is calculated from TxID and OutputIndex
id ids.ID
}
@ -35,6 +37,10 @@ func (utxo *UTXOID) InputID() ids.ID {
return utxo.id
}
// Symbolic returns if this is the ID of a UTXO in the DB, or if it is a
// symbolic input
func (utxo *UTXOID) Symbolic() bool { return utxo.Symbol }
// Verify implements the verify.Verifiable interface
func (utxo *UTXOID) Verify() error {
switch {

View File

@ -1,7 +1,7 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"testing"

View File

@ -1,7 +1,7 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
package ava
import (
"bytes"
@ -34,9 +34,6 @@ func TestUTXOVerifyEmpty(t *testing.T) {
func TestUTXOSerialize(t *testing.T) {
c := codec.NewDefault()
c.RegisterType(&BaseTx{})
c.RegisterType(&CreateAssetTx{})
c.RegisterType(&OperationTx{})
c.RegisterType(&secp256k1fx.MintOutput{})
c.RegisterType(&secp256k1fx.TransferOutput{})
c.RegisterType(&secp256k1fx.MintInput{})
@ -57,7 +54,7 @@ func TestUTXOSerialize(t *testing.T) {
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
// output:
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x30, 0x39, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xd4, 0x31, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x03,

View File

@ -184,10 +184,7 @@ func (tx *addDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (*ve
// If this proposal is aborted, chain state doesn't change
onAbortDB := versiondb.New(db)
onAccept := func() {
tx.vm.resetTimer()
}
return onCommitDB, onAbortDB, onAccept, nil, nil
return onCommitDB, onAbortDB, tx.vm.resetTimer, nil, nil
}
// InitiallyPrefersCommit returns true if the proposed validators start time is

View File

@ -162,7 +162,7 @@ func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) (
}
var subnet *CreateSubnetTx
for _, sn := range subnets {
if sn.ID.Equals(tx.SubnetID()) {
if sn.id.Equals(tx.SubnetID()) {
subnet = sn
break
}

View File

@ -28,7 +28,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix()),
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -48,7 +48,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix()),
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID+1,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -67,7 +67,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix()),
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -87,7 +87,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix()),
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -107,7 +107,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix()),
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -126,7 +126,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix())-1,
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -147,7 +147,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateStartTime.Add(MinimumStakingDuration).Unix())-1,
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -167,7 +167,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateStartTime.Add(MaximumStakingDuration).Unix())+1,
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -187,7 +187,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix()),
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -212,7 +212,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix())+1,
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -235,7 +235,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix()),
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -245,7 +245,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
}
_, _, _, _, err = tx.SemanticVerify(vm.DB)
if err != nil {
t.Log(testSubnet1.ID)
t.Log(testSubnet1.id)
subnets, err := vm.getSubnets(vm.DB)
if err != nil {
t.Fatal(err)
@ -253,7 +253,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
if len(subnets) == 0 {
t.Fatal("no subnets found")
}
t.Logf("subnets[0].ID: %v", subnets[0].ID)
t.Logf("subnets[0].ID: %v", subnets[0].id)
t.Fatal(err)
}
@ -290,7 +290,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(DSStartTime.Unix()), // start validating non-default subnet before default subnet
uint64(DSEndTime.Unix()),
pendingDSValidatorID,
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -324,7 +324,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(DSStartTime.Unix())-1, // start validating non-default subnet before default subnet
uint64(DSEndTime.Unix()),
pendingDSValidatorID,
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -346,7 +346,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(DSStartTime.Unix()),
uint64(DSEndTime.Unix())+1, // stop validating non-default subnet after stopping validating default subnet
pendingDSValidatorID,
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -368,7 +368,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(DSStartTime.Unix()), // same start time as for default subnet
uint64(DSEndTime.Unix()), // same end time as for default subnet
pendingDSValidatorID,
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,
@ -394,7 +394,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(newTimestamp.Unix()), // start time
uint64(newTimestamp.Add(MinimumStakingDuration).Unix()), // end time
defaultKey.PublicKey().Address(), // node ID
testSubnet1.ID, // subnet ID
testSubnet1.id, // subnet ID
testNetworkID, // network ID
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey, // tx fee payer
@ -429,7 +429,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()), // start time
uint64(defaultValidateEndTime.Unix()), // end time
defaultKey.PublicKey().Address(), // node ID
testSubnet1.ID, // subnet ID
testSubnet1.id, // subnet ID
testNetworkID, // network ID
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
newAcctKey.(*crypto.PrivateKeySECP256K1R), // tx fee payer
@ -451,7 +451,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()), // start time
uint64(defaultValidateEndTime.Unix()), // end time
defaultKey.PublicKey().Address(), // node ID
testSubnet1.ID, // subnet ID
testSubnet1.id, // subnet ID
testNetworkID, // network ID
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey, // tx fee payer
@ -465,7 +465,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
SortByStartTime: false,
Txs: []TimedTx{tx},
},
testSubnet1.ID,
testSubnet1.id,
)
// Node with ID nodeIDKey.PublicKey().Address() now validating subnet with ID testSubnet1.ID
@ -475,7 +475,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(defaultValidateStartTime.Unix()), // start time
uint64(defaultValidateEndTime.Unix()), // end time
defaultKey.PublicKey().Address(), // node ID
testSubnet1.ID, // subnet ID
testSubnet1.id, // subnet ID
testNetworkID, // network ID
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey, // tx fee payer
@ -494,7 +494,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
&EventHeap{
SortByStartTime: false,
},
testSubnet1.ID,
testSubnet1.id,
)
// Case 9: Too many signatures
@ -504,7 +504,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(defaultGenesisTime.Unix()), // start time
uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix())+1, // end time
keys[0].PublicKey().Address(), // node ID
testSubnet1.ID, // subnet ID
testSubnet1.id, // subnet ID
testNetworkID, // network ID
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1], testSubnet1ControlKeys[2]},
defaultKey, // tx fee payer
@ -525,7 +525,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(defaultGenesisTime.Unix()), // start time
uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix()), // end time
keys[0].PublicKey().Address(), // node ID
testSubnet1.ID, // subnet ID
testSubnet1.id, // subnet ID
testNetworkID, // network ID
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[2]},
defaultKey, // tx fee payer
@ -546,7 +546,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(defaultGenesisTime.Unix()), // start time
uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix()), // end time
keys[0].PublicKey().Address(), // node ID
testSubnet1.ID, // subnet ID
testSubnet1.id, // subnet ID
testNetworkID, // network ID
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], keys[3]},
defaultKey, // tx fee payer
@ -568,7 +568,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
uint64(defaultGenesisTime.Unix())+1, // start time
uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix())+1, // end time
defaultKey.PublicKey().Address(), // node ID
testSubnet1.ID, // subnet ID
testSubnet1.id, // subnet ID
testNetworkID, // network ID
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey, // tx fee payer
@ -582,7 +582,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) {
SortByStartTime: true,
Txs: []TimedTx{tx},
},
testSubnet1.ID,
testSubnet1.id,
)
// Node with ID nodeIDKey.PublicKey().Address() now pending validator for subnet with ID testSubnet1.ID
@ -604,7 +604,7 @@ func TestAddNonDefaultSubnetValidatorMarshal(t *testing.T) {
uint64(defaultValidateStartTime.Unix()),
uint64(defaultValidateEndTime.Unix()),
defaultKey.PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
defaultKey,

View File

@ -105,15 +105,15 @@ func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Databa
return nil, nil, nil, nil, err
}
for _, subnet := range subnets {
current, pending, err := tx.vm.calculateValidators(db, tx.Timestamp(), subnet.ID)
current, pending, err := tx.vm.calculateValidators(db, tx.Timestamp(), subnet.id)
if err != nil {
return nil, nil, nil, nil, err
}
if err := tx.vm.putCurrentValidators(onCommitDB, current, subnet.ID); err != nil {
if err := tx.vm.putCurrentValidators(onCommitDB, current, subnet.id); err != nil {
return nil, nil, nil, nil, err
}
if err := tx.vm.putPendingValidators(onCommitDB, pending, subnet.ID); err != nil {
if err := tx.vm.putPendingValidators(onCommitDB, pending, subnet.id); err != nil {
return nil, nil, nil, nil, err
}
}
@ -127,7 +127,7 @@ func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Databa
return
}
for _, subnet := range subnets {
if err := tx.vm.updateValidators(subnet.ID); err != nil {
if err := tx.vm.updateValidators(subnet.id); err != nil {
tx.vm.Ctx.Log.Debug("failed to update validators on the default subnet: %s", err)
}
}

View File

@ -0,0 +1,154 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package platformvm
import (
"errors"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/vms/components/core"
)
var (
errConflictingParentTxs = errors.New("block contains a transaction that conflicts with a transaction in a parent block")
)
// AtomicTx is an operation that can be decided without being proposed, but must have special control over database commitment
type AtomicTx interface {
initialize(vm *VM) error
ID() ids.ID
// UTXOs this tx consumes
InputUTXOs() ids.Set
// Attempt to verify this transaction with the provided state. The provided
// database can be modified arbitrarily.
SemanticVerify(database.Database) error
Accept(database.Batch) error
}
// AtomicBlock being accepted results in the transaction contained in the
// block to be accepted and committed to the chain.
type AtomicBlock struct {
CommonDecisionBlock `serialize:"true"`
Tx AtomicTx `serialize:"true"`
inputs ids.Set
}
// initialize this block
func (ab *AtomicBlock) initialize(vm *VM, bytes []byte) error {
if err := ab.CommonDecisionBlock.initialize(vm, bytes); err != nil {
return err
}
return ab.Tx.initialize(vm)
}
// Reject implements the snowman.Block interface
func (ab *AtomicBlock) conflicts(s ids.Set) bool {
if ab.Status() == choices.Accepted {
return false
}
if ab.inputs.Overlaps(s) {
return true
}
return ab.parentBlock().conflicts(s)
}
// Verify this block performs a valid state transition.
//
// The parent block must be a proposal
//
// This function also sets onAcceptDB database if the verification passes.
func (ab *AtomicBlock) Verify() error {
parentBlock := ab.parentBlock()
ab.inputs = ab.Tx.InputUTXOs()
if parentBlock.conflicts(ab.inputs) {
return errConflictingParentTxs
}
// AtomicBlock is not a modifier on a proposal block, so its parent must be
// a decision.
parent, ok := parentBlock.(decision)
if !ok {
return errInvalidBlockType
}
pdb := parent.onAccept()
ab.onAcceptDB = versiondb.New(pdb)
if err := ab.Tx.SemanticVerify(ab.onAcceptDB); err != nil {
return err
}
ab.vm.currentBlocks[ab.ID().Key()] = ab
ab.parentBlock().addChild(ab)
return nil
}
// Accept implements the snowman.Block interface
func (ab *AtomicBlock) Accept() {
ab.vm.Ctx.Log.Verbo("Accepting block with ID %s", ab.ID())
ab.CommonBlock.Accept()
// Update the state of the chain in the database
if err := ab.onAcceptDB.Commit(); err != nil {
ab.vm.Ctx.Log.Error("unable to commit onAcceptDB")
}
batch, err := ab.vm.DB.CommitBatch()
if err != nil {
ab.vm.Ctx.Log.Fatal("unable to commit vm's DB")
}
defer ab.vm.DB.Abort()
if err := ab.Tx.Accept(batch); err != nil {
ab.vm.Ctx.Log.Error("unable to atomically commit block")
}
for _, child := range ab.children {
child.setBaseDatabase(ab.vm.DB)
}
if ab.onAcceptFunc != nil {
ab.onAcceptFunc()
}
parent := ab.parentBlock()
// remove this block and its parent from memory
parent.free()
ab.free()
}
// newAtomicBlock returns a new *AtomicBlock where the block's parent, a
// decision block, has ID [parentID].
func (vm *VM) newAtomicBlock(parentID ids.ID, tx AtomicTx) (*AtomicBlock, error) {
ab := &AtomicBlock{
CommonDecisionBlock: CommonDecisionBlock{
CommonBlock: CommonBlock{
Block: core.NewBlock(parentID),
vm: vm,
},
},
Tx: tx,
}
// We serialize this block as a Block so that it can be deserialized into a
// Block
blk := Block(ab)
bytes, err := Codec.Marshal(&blk)
if err != nil {
return nil, err
}
ab.Block.Initialize(bytes, vm.SnowmanVM)
return ab, nil
}

View File

@ -6,10 +6,12 @@ package platformvm
import (
"errors"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/components/missing"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/snow/consensus/snowman"
"github.com/ava-labs/gecko/vms/components/core"
)
@ -87,6 +89,8 @@ type Block interface {
// [bytes] is the byte representation of this block
initialize(vm *VM, bytes []byte) error
conflicts(ids.Set) bool
// parentBlock returns the parent block, similarly to Parent. However, it
// provides the more specific staking.Block interface.
parentBlock() Block
@ -142,6 +146,14 @@ func (cb *CommonBlock) free() {
cb.children = nil
}
// Reject implements the snowman.Block interface
func (cb *CommonBlock) conflicts(s ids.Set) bool {
if cb.Status() == choices.Accepted {
return false
}
return cb.parentBlock().conflicts(s)
}
// Parent returns this block's parent
func (cb *CommonBlock) Parent() snowman.Block {
parent := cb.parentBlock()

View File

@ -148,8 +148,8 @@ func (tx *CreateChainTx) SemanticVerify(db database.Database) (func(), error) {
chainParams.FxAliases = append(chainParams.FxAliases, fxID.String())
}
// TODO: Not sure how else to make this not nil pointer error during tests
if tx.vm.ChainManager != nil {
tx.vm.ChainManager.CreateChain(chainParams)
if tx.vm.chainManager != nil {
tx.vm.chainManager.CreateChain(chainParams)
}
}

View File

@ -23,12 +23,6 @@ var (
// UnsignedCreateSubnetTx is an unsigned proposal to create a new subnet
type UnsignedCreateSubnetTx struct {
// The VM this tx exists within
vm *VM
// ID is this transaction's ID
ID ids.ID
// NetworkID is the ID of the network this tx was issued on
NetworkID uint32 `serialize:"true"`
@ -47,6 +41,12 @@ type UnsignedCreateSubnetTx struct {
type CreateSubnetTx struct {
UnsignedCreateSubnetTx `serialize:"true"`
// The VM this tx exists within
vm *VM
// ID is this transaction's ID
id ids.ID
// The public key that signed this transaction
// The transaction fee will be paid from the corresponding account
// (ie the account whose ID is [key].Address())
@ -60,6 +60,9 @@ type CreateSubnetTx struct {
bytes []byte
}
// ID returns the ID of this tx
func (tx *CreateSubnetTx) ID() ids.ID { return tx.id }
// SyntacticVerify nil iff [tx] is syntactically valid.
// If [tx] is valid, this method sets [tx.key]
func (tx *CreateSubnetTx) SyntacticVerify() error {
@ -68,7 +71,7 @@ func (tx *CreateSubnetTx) SyntacticVerify() error {
return errNilTx
case tx.key != nil:
return nil // Only verify the transaction once
case tx.ID.IsZero():
case tx.id.IsZero():
return errInvalidID
case tx.NetworkID != tx.vm.Ctx.NetworkID:
return errWrongNetworkID
@ -106,8 +109,8 @@ func (tx *CreateSubnetTx) SemanticVerify(db database.Database) (func(), error) {
}
for _, subnet := range subnets {
if subnet.ID.Equals(tx.ID) {
return nil, fmt.Errorf("there is already a subnet with ID %s", tx.ID)
if subnet.id.Equals(tx.id) {
return nil, fmt.Errorf("there is already a subnet with ID %s", tx.id)
}
}
subnets = append(subnets, tx) // add new subnet
@ -152,7 +155,7 @@ func (tx *CreateSubnetTx) initialize(vm *VM) error {
return err
}
tx.bytes = txBytes
tx.ID = ids.NewID(hashing.ComputeHash256Array(txBytes))
tx.id = ids.NewID(hashing.ComputeHash256Array(txBytes))
return nil
}
@ -160,15 +163,12 @@ func (vm *VM) newCreateSubnetTx(networkID uint32, nonce uint64, controlKeys []id
threshold uint16, payerKey *crypto.PrivateKeySECP256K1R,
) (*CreateSubnetTx, error) {
tx := &CreateSubnetTx{
UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{
vm: vm,
tx := &CreateSubnetTx{UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{
NetworkID: networkID,
Nonce: nonce,
ControlKeys: controlKeys,
Threshold: threshold,
},
}
}}
unsignedIntf := interface{}(&tx.UnsignedCreateSubnetTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf)

View File

@ -18,7 +18,7 @@ func TestTxHeapStart(t *testing.T) {
123, // stake amount
1, // startTime
3, // endTime
ids.NewShortID([20]byte{1}), // node ID
ids.NewShortID([20]byte{}), // node ID
ids.NewShortID([20]byte{1, 2, 3, 4, 5, 6, 7}), // destination
0, // shares
0, // network ID
@ -33,7 +33,7 @@ func TestTxHeapStart(t *testing.T) {
123, // stake amount
1, // startTime
3, // endTime
ids.NewShortID([20]byte{}), // node ID
ids.NewShortID([20]byte{1}), // node ID
ids.NewShortID([20]byte{1, 2, 3, 4, 5, 6, 7}), // destination
0, // shares
0, // network ID
@ -85,7 +85,7 @@ func TestTxHeapStop(t *testing.T) {
123, // stake amount
1, // startTime
3, // endTime
ids.NewShortID([20]byte{1}), // node ID
ids.NewShortID([20]byte{}), // node ID
ids.NewShortID([20]byte{1, 2, 3, 4, 5, 6, 7}), // destination
0, // shares
0, // network ID
@ -100,7 +100,7 @@ func TestTxHeapStop(t *testing.T) {
123, // stake amount
1, // startTime
3, // endTime
ids.NewShortID([20]byte{}), // node ID
ids.NewShortID([20]byte{1}), // node ID
ids.NewShortID([20]byte{1, 2, 3, 4, 5, 6, 7}), // destination
0, // shares
0, // network ID

193
vms/platformvm/export_tx.go Normal file
View File

@ -0,0 +1,193 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package platformvm
import (
"errors"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/math"
"github.com/ava-labs/gecko/vms/components/ava"
)
var (
errNoExportOutputs = errors.New("no export outputs")
errOutputsNotSorted = errors.New("outputs not sorted")
)
// UnsignedExportTx is an unsigned ExportTx
type UnsignedExportTx struct {
// ID of the network this blockchain exists on
NetworkID uint32 `serialize:"true"`
// Next unused nonce of account paying for this transaction.
Nonce uint64 `serialize:"true"`
Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction
}
// ExportTx exports funds to the AVM
type ExportTx struct {
UnsignedExportTx `serialize:"true"`
Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"`
vm *VM
id ids.ID
key crypto.PublicKey // public key of transaction signer
bytes []byte
}
func (tx *ExportTx) initialize(vm *VM) error {
tx.vm = vm
txBytes, err := Codec.Marshal(tx) // byte repr. of the signed tx
tx.bytes = txBytes
tx.id = ids.NewID(hashing.ComputeHash256Array(txBytes))
return err
}
// ID of this transaction
func (tx *ExportTx) ID() ids.ID { return tx.id }
// Key returns the public key of the signer of this transaction
// Precondition: tx.Verify() has been called and returned nil
func (tx *ExportTx) Key() crypto.PublicKey { return tx.key }
// Bytes returns the byte representation of an ExportTx
func (tx *ExportTx) Bytes() []byte { return tx.bytes }
// InputUTXOs returns an empty set
func (tx *ExportTx) InputUTXOs() ids.Set { return ids.Set{} }
// SyntacticVerify this transaction is well-formed
// Also populates [tx.Key] with the public key that signed this transaction
func (tx *ExportTx) SyntacticVerify() error {
switch {
case tx == nil:
return errNilTx
case tx.key != nil:
return nil // Only verify the transaction once
case tx.NetworkID != tx.vm.Ctx.NetworkID: // verify the transaction is on this network
return errWrongNetworkID
case tx.id.IsZero():
return errInvalidID
case len(tx.Outs) == 0:
return errNoExportOutputs
}
for _, out := range tx.Outs {
if err := out.Verify(); err != nil {
return err
}
if !out.AssetID().Equals(tx.vm.ava) {
return errUnknownAsset
}
}
if !ava.IsSortedTransferableOutputs(tx.Outs, Codec) {
return errOutputsNotSorted
}
unsignedIntf := interface{}(&tx.UnsignedExportTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr of unsigned tx
if err != nil {
return err
}
key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:])
if err != nil {
return err
}
tx.key = key
return nil
}
// SemanticVerify this transaction is valid.
func (tx *ExportTx) SemanticVerify(db database.Database) error {
if err := tx.SyntacticVerify(); err != nil {
return err
}
amount := uint64(0)
for _, out := range tx.Outs {
newAmount, err := math.Add64(out.Out.Amount(), amount)
if err != nil {
return err
}
amount = newAmount
}
accountID := tx.key.Address()
account, err := tx.vm.getAccount(db, accountID)
if err != nil {
return errDBAccount
}
account, err = account.Remove(amount, tx.Nonce)
if err != nil {
return err
}
return tx.vm.putAccount(db, account)
}
// Accept this transaction.
func (tx *ExportTx) Accept(batch database.Batch) error {
txID := tx.ID()
smDB := tx.vm.Ctx.SharedMemory.GetDatabase(tx.vm.avm)
defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(tx.vm.avm)
vsmDB := versiondb.New(smDB)
state := ava.NewPrefixedState(vsmDB, Codec)
for i, out := range tx.Outs {
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{
TxID: txID,
OutputIndex: uint32(i),
},
Asset: ava.Asset{ID: out.AssetID()},
Out: out.Out,
}
if err := state.FundPlatformUTXO(utxo); err != nil {
return err
}
}
sharedBatch, err := vsmDB.CommitBatch()
if err != nil {
return err
}
return atomic.WriteAll(batch, sharedBatch)
}
func (vm *VM) newExportTx(nonce uint64, networkID uint32, outs []*ava.TransferableOutput, from *crypto.PrivateKeySECP256K1R) (*ExportTx, error) {
ava.SortTransferableOutputs(outs, Codec)
tx := &ExportTx{UnsignedExportTx: UnsignedExportTx{
NetworkID: networkID,
Nonce: nonce,
Outs: outs,
}}
unsignedIntf := interface{}(&tx.UnsignedExportTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction
if err != nil {
return nil, err
}
sig, err := key.Sign(unsignedBytes)
if err != nil {
return nil, err
}
copy(tx.Sig[:], sig)
return tx, tx.initialize(vm)
}

View File

@ -18,12 +18,16 @@ var (
type Factory struct {
ChainManager chains.Manager
Validators validators.Manager
AVA ids.ID
AVM ids.ID
}
// New returns a new instance of the Platform Chain
func (f *Factory) New() interface{} {
return &VM{
ChainManager: f.ChainManager,
Validators: f.Validators,
chainManager: f.ChainManager,
validators: f.Validators,
ava: f.AVA,
avm: f.AVM,
}
}

268
vms/platformvm/import_tx.go Normal file
View File

@ -0,0 +1,268 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package platformvm
import (
"errors"
"fmt"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/math"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/verify"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
var (
errAssetIDMismatch = errors.New("asset IDs in the input don't match the utxo")
errWrongNumberOfCredentials = errors.New("should have the same number of credentials as inputs")
errNoImportInputs = errors.New("no import inputs")
errInputsNotSortedUnique = errors.New("inputs not sorted and unique")
errPublicKeySignatureMismatch = errors.New("signature doesn't match public key")
errUnknownAsset = errors.New("unknown asset ID")
)
// UnsignedImportTx is an unsigned ImportTx
type UnsignedImportTx struct {
// ID of the network this blockchain exists on
NetworkID uint32 `serialize:"true"`
// Next unused nonce of account paying the transaction fee and receiving the
// inputs of this transaction.
Nonce uint64 `serialize:"true"`
// Account that this transaction is being sent by. This is needed to ensure the Credentials are replay safe.
Account ids.ShortID `serialize:"true"`
Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction
}
// ImportTx imports funds from the AVM
type ImportTx struct {
UnsignedImportTx `serialize:"true"`
Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"`
Creds []verify.Verifiable `serialize:"true"` // The credentials of this transaction
vm *VM
id ids.ID
key crypto.PublicKey // public key of transaction signer
unsignedBytes []byte
bytes []byte
}
func (tx *ImportTx) initialize(vm *VM) error {
tx.vm = vm
txBytes, err := Codec.Marshal(tx) // byte repr. of the signed tx
tx.bytes = txBytes
tx.id = ids.NewID(hashing.ComputeHash256Array(txBytes))
return err
}
// ID of this transaction
func (tx *ImportTx) ID() ids.ID { return tx.id }
// Key returns the public key of the signer of this transaction
// Precondition: tx.Verify() has been called and returned nil
func (tx *ImportTx) Key() crypto.PublicKey { return tx.key }
// UnsignedBytes returns the unsigned byte representation of an ImportTx
func (tx *ImportTx) UnsignedBytes() []byte { return tx.unsignedBytes }
// Bytes returns the byte representation of an ImportTx
func (tx *ImportTx) Bytes() []byte { return tx.bytes }
// InputUTXOs returns an empty set
func (tx *ImportTx) InputUTXOs() ids.Set {
set := ids.Set{}
for _, in := range tx.Ins {
set.Add(in.InputID())
}
return set
}
// SyntacticVerify this transaction is well-formed
// Also populates [tx.Key] with the public key that signed this transaction
func (tx *ImportTx) SyntacticVerify() error {
switch {
case tx == nil:
return errNilTx
case tx.key != nil:
return nil // Only verify the transaction once
case tx.NetworkID != tx.vm.Ctx.NetworkID: // verify the transaction is on this network
return errWrongNetworkID
case tx.id.IsZero():
return errInvalidID
case len(tx.Ins) == 0:
return errNoImportInputs
case len(tx.Ins) != len(tx.Creds):
return errWrongNumberOfCredentials
}
for _, in := range tx.Ins {
if err := in.Verify(); err != nil {
return err
}
if !in.AssetID().Equals(tx.vm.ava) {
return errUnknownAsset
}
}
if !ava.IsSortedAndUniqueTransferableInputs(tx.Ins) {
return errInputsNotSortedUnique
}
for _, cred := range tx.Creds {
if err := cred.Verify(); err != nil {
return err
}
}
unsignedIntf := interface{}(&tx.UnsignedImportTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr of unsigned tx
if err != nil {
return err
}
key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:])
if err != nil {
return err
}
if !tx.Account.Equals(key.Address()) {
return errPublicKeySignatureMismatch
}
tx.key = key
tx.unsignedBytes = unsignedBytes
return nil
}
// SemanticVerify this transaction is valid.
func (tx *ImportTx) SemanticVerify(db database.Database) error {
if err := tx.SyntacticVerify(); err != nil {
return err
}
amount := uint64(0)
for _, in := range tx.Ins {
newAmount, err := math.Add64(in.In.Amount(), amount)
if err != nil {
return err
}
amount = newAmount
}
// Deduct tx fee from payer's account
account, err := tx.vm.getAccount(db, tx.Key().Address())
if err != nil {
return err
}
account, err = account.Remove(0, tx.Nonce)
if err != nil {
return err
}
account, err = account.Add(amount)
if err != nil {
return err
}
if err := tx.vm.putAccount(db, account); err != nil {
return err
}
smDB := tx.vm.Ctx.SharedMemory.GetDatabase(tx.vm.avm)
defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(tx.vm.avm)
state := ava.NewPrefixedState(smDB, Codec)
for i, in := range tx.Ins {
cred := tx.Creds[i]
utxoID := in.UTXOID.InputID()
utxo, err := state.AVMUTXO(utxoID)
if err != nil {
return err
}
utxoAssetID := utxo.AssetID()
inAssetID := in.AssetID()
if !utxoAssetID.Equals(inAssetID) {
return errAssetIDMismatch
}
if err := tx.vm.fx.VerifyTransfer(tx, utxo.Out, in.In, cred); err != nil {
return err
}
}
return nil
}
// Accept this transaction.
func (tx *ImportTx) Accept(batch database.Batch) error {
smDB := tx.vm.Ctx.SharedMemory.GetDatabase(tx.vm.avm)
defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(tx.vm.avm)
vsmDB := versiondb.New(smDB)
state := ava.NewPrefixedState(vsmDB, Codec)
for _, in := range tx.Ins {
utxoID := in.UTXOID.InputID()
if err := state.SpendAVMUTXO(utxoID); err != nil {
return err
}
}
sharedBatch, err := vsmDB.CommitBatch()
if err != nil {
return err
}
return atomic.WriteAll(batch, sharedBatch)
}
func (vm *VM) newImportTx(nonce uint64, networkID uint32, ins []*ava.TransferableInput, from [][]*crypto.PrivateKeySECP256K1R, to *crypto.PrivateKeySECP256K1R) (*ImportTx, error) {
ava.SortTransferableInputsWithSigners(ins, from)
tx := &ImportTx{UnsignedImportTx: UnsignedImportTx{
NetworkID: networkID,
Nonce: nonce,
Account: key.PublicKey().Address(),
Ins: ins,
}}
unsignedIntf := interface{}(&tx.UnsignedImportTx)
unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction
if err != nil {
return nil, err
}
hash := hashing.ComputeHash256(unsignedBytes)
for _, credKeys := range from {
cred := &secp256k1fx.Credential{}
for _, key := range credKeys {
sig, err := key.SignHash(hash)
if err != nil {
return nil, fmt.Errorf("problem creating transaction: %w", err)
}
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
cred.Sigs = append(cred.Sigs, fixedSig)
}
tx.Creds = append(tx.Creds, cred)
}
sig, err := key.SignHash(hash)
if err != nil {
return nil, err
}
copy(tx.Sig[:], sig)
return tx, tx.initialize(vm)
}

View File

@ -16,7 +16,11 @@ import (
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/json"
"github.com/ava-labs/gecko/utils/math"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
var (
@ -97,7 +101,7 @@ func (service *Service) GetSubnets(_ *http.Request, args *GetSubnetsArgs, respon
response.Subnets = make([]APISubnet, len(subnets))
for i, subnet := range subnets {
response.Subnets[i] = APISubnet{
ID: subnet.ID,
ID: subnet.id,
ControlKeys: subnet.ControlKeys,
Threshold: json.Uint16(subnet.Threshold),
}
@ -108,10 +112,10 @@ func (service *Service) GetSubnets(_ *http.Request, args *GetSubnetsArgs, respon
idsSet := ids.Set{}
idsSet.Add(args.IDs...)
for _, subnet := range subnets {
if idsSet.Contains(subnet.ID) {
if idsSet.Contains(subnet.id) {
response.Subnets = append(response.Subnets,
APISubnet{
ID: subnet.ID,
ID: subnet.id,
ControlKeys: subnet.ControlKeys,
Threshold: json.Uint16(subnet.Threshold),
},
@ -248,7 +252,7 @@ func (service *Service) SampleValidators(_ *http.Request, args *SampleValidators
args.SubnetID = DefaultSubnetID
}
validators, ok := service.vm.Validators.GetValidatorSet(args.SubnetID)
validators, ok := service.vm.validators.GetValidatorSet(args.SubnetID)
if !ok {
return fmt.Errorf("couldn't get validators of subnet with ID %s. Does it exist?", args.SubnetID)
}
@ -432,6 +436,11 @@ type genericTx struct {
******************************************************
*/
// CreateTxResponse is the response from calls to create a transaction
type CreateTxResponse struct {
UnsignedTx formatting.CB58 `json:"unsignedTx"`
}
// AddDefaultSubnetValidatorArgs are the arguments to AddDefaultSubnetValidator
type AddDefaultSubnetValidatorArgs struct {
APIDefaultSubnetValidator
@ -440,15 +449,9 @@ type AddDefaultSubnetValidatorArgs struct {
PayerNonce json.Uint64 `json:"payerNonce"`
}
// AddDefaultSubnetValidatorResponse is the response from a call to AddDefaultSubnetValidator
type AddDefaultSubnetValidatorResponse struct {
// The unsigned transaction
UnsignedTx formatting.CB58 `json:"unsignedTx"`
}
// AddDefaultSubnetValidator returns an unsigned transaction to add a validator to the default subnet
// The returned unsigned transaction should be signed using Sign()
func (service *Service) AddDefaultSubnetValidator(_ *http.Request, args *AddDefaultSubnetValidatorArgs, reply *AddDefaultSubnetValidatorResponse) error {
func (service *Service) AddDefaultSubnetValidator(_ *http.Request, args *AddDefaultSubnetValidatorArgs, reply *CreateTxResponse) error {
service.vm.Ctx.Log.Debug("platform.AddDefaultSubnetValidator called")
if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID
@ -490,16 +493,10 @@ type AddDefaultSubnetDelegatorArgs struct {
PayerNonce json.Uint64 `json:"payerNonce"`
}
// AddDefaultSubnetDelegatorResponse is the response from a call to AddDefaultSubnetDelegator
type AddDefaultSubnetDelegatorResponse struct {
// The unsigned transaction
UnsignedTx formatting.CB58 `json:"unsignedTx"`
}
// AddDefaultSubnetDelegator returns an unsigned transaction to add a delegator
// to the default subnet
// The returned unsigned transaction should be signed using Sign()
func (service *Service) AddDefaultSubnetDelegator(_ *http.Request, args *AddDefaultSubnetDelegatorArgs, reply *AddDefaultSubnetDelegatorResponse) error {
func (service *Service) AddDefaultSubnetDelegator(_ *http.Request, args *AddDefaultSubnetDelegatorArgs, reply *CreateTxResponse) error {
service.vm.Ctx.Log.Debug("platform.AddDefaultSubnetDelegator called")
if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID
@ -541,15 +538,9 @@ type AddNonDefaultSubnetValidatorArgs struct {
PayerNonce json.Uint64 `json:"payerNonce"`
}
// AddNonDefaultSubnetValidatorResponse is the response from a call to AddNonDefaultSubnetValidator
type AddNonDefaultSubnetValidatorResponse struct {
// The unsigned transaction
UnsignedTx formatting.CB58 `json:"unsignedTx"`
}
// AddNonDefaultSubnetValidator adds a validator to a subnet other than the default subnet
// Returns the unsigned transaction, which must be signed using Sign
func (service *Service) AddNonDefaultSubnetValidator(_ *http.Request, args *AddNonDefaultSubnetValidatorArgs, response *AddNonDefaultSubnetValidatorResponse) error {
func (service *Service) AddNonDefaultSubnetValidator(_ *http.Request, args *AddNonDefaultSubnetValidatorArgs, response *CreateTxResponse) error {
tx := addNonDefaultSubnetValidatorTx{
UnsignedAddNonDefaultSubnetValidatorTx: UnsignedAddNonDefaultSubnetValidatorTx{
SubnetValidator: SubnetValidator{
@ -583,6 +574,83 @@ func (service *Service) AddNonDefaultSubnetValidator(_ *http.Request, args *AddN
return nil
}
// CreateSubnetArgs are the arguments to CreateSubnet
type CreateSubnetArgs struct {
// The ID member of APISubnet is ignored
APISubnet
// Nonce of the account that pays the transaction fee
PayerNonce json.Uint64 `json:"payerNonce"`
}
// CreateSubnet returns an unsigned transaction to create a new subnet.
// The unsigned transaction must be signed with the key of [args.Payer]
func (service *Service) CreateSubnet(_ *http.Request, args *CreateSubnetArgs, response *CreateTxResponse) error {
service.vm.Ctx.Log.Debug("platform.createSubnet called")
// Create the transaction
tx := CreateSubnetTx{
UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{
NetworkID: service.vm.Ctx.NetworkID,
Nonce: uint64(args.PayerNonce),
ControlKeys: args.ControlKeys,
Threshold: uint16(args.Threshold),
},
key: nil,
Sig: [65]byte{},
bytes: nil,
}
txBytes, err := Codec.Marshal(genericTx{Tx: &tx})
if err != nil {
return errCreatingTransaction
}
response.UnsignedTx.Bytes = txBytes
return nil
}
// CreateExportTxArgs are the arguments to CreateExportTx
type CreateExportTxArgs struct {
// ID of the address that will receive the exported funds
To ids.ShortID `json:"to"`
// Nonce of the account that pays the transaction fee
PayerNonce json.Uint64 `json:"payerNonce"`
Amount json.Uint64 `json:"amount"`
}
// CreateExportTx returns an unsigned transaction to export funds.
// The unsigned transaction must be signed with the key of [args.Payer]
func (service *Service) CreateExportTx(_ *http.Request, args *CreateExportTxArgs, response *CreateTxResponse) error {
service.vm.Ctx.Log.Debug("platform.createExportTx called")
// Create the transaction
tx := ExportTx{UnsignedExportTx: UnsignedExportTx{
NetworkID: service.vm.Ctx.NetworkID,
Nonce: uint64(args.PayerNonce),
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{ID: service.vm.ava},
Out: &secp256k1fx.TransferOutput{
Amt: uint64(args.Amount),
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{args.To},
},
},
}},
}}
txBytes, err := Codec.Marshal(genericTx{Tx: &tx})
if err != nil {
return errCreatingTransaction
}
response.UnsignedTx.Bytes = txBytes
return nil
}
/*
******************************************************
**************** Sign/Issue Txs **********************
@ -606,7 +674,7 @@ type SignArgs struct {
// SignResponse is the response from Sign
type SignResponse struct {
// The signed bytes
Tx formatting.CB58
Tx formatting.CB58 `json:"tx"`
}
// Sign [args.bytes]
@ -642,6 +710,8 @@ func (service *Service) Sign(_ *http.Request, args *SignArgs, reply *SignRespons
genTx.Tx, err = service.signAddNonDefaultSubnetValidatorTx(tx, key)
case *CreateSubnetTx:
genTx.Tx, err = service.signCreateSubnetTx(tx, key)
case *ExportTx:
genTx.Tx, err = service.signExportTx(tx, key)
default:
err = errors.New("Could not parse given tx. Must be one of: addDefaultSubnetValidatorTx, addNonDefaultSubnetValidatorTx, createSubnetTx")
}
@ -722,6 +792,29 @@ func (service *Service) signCreateSubnetTx(tx *CreateSubnetTx, key *crypto.Priva
return tx, nil
}
// Sign [xt] with [key]
func (service *Service) signExportTx(tx *ExportTx, key *crypto.PrivateKeySECP256K1R) (*ExportTx, error) {
service.vm.Ctx.Log.Debug("platform.signAddDefaultSubnetValidatorTx called")
// TODO: Should we check if tx is already signed?
unsignedIntf := interface{}(&tx.UnsignedExportTx)
unsignedTxBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return nil, fmt.Errorf("error serializing unsigned tx: %v", err)
}
sig, err := key.Sign(unsignedTxBytes)
if err != nil {
return nil, errors.New("error while signing")
}
if len(sig) != crypto.SECP256K1RSigLen {
return nil, fmt.Errorf("expected signature to be length %d but was length %d", crypto.SECP256K1RSigLen, len(sig))
}
copy(tx.Sig[:], sig)
return tx, nil
}
// Signs an unsigned or partially signed addNonDefaultSubnetValidatorTx with [key]
// If [key] is a control key for the subnet and there is an empty spot in tx.ControlSigs, signs there
// If [key] is a control key for the subnet and there is no empty spot in tx.ControlSigs, signs as payer
@ -775,6 +868,141 @@ func (service *Service) signAddNonDefaultSubnetValidatorTx(tx *addNonDefaultSubn
return tx, nil
}
// CreateImportTxArgs are the arguments to CreateImportTx
type CreateImportTxArgs struct {
// Addresses that can be used to sign the import
ImportAddresses []ids.ShortID `json:"importAddresses"`
// ID of the account that will receive the imported funds, and pay the
// import fee
AccountID ids.ShortID `json:"accountID"`
// Nonce of the account that pays the transaction fee
PayerNonce json.Uint64 `json:"payerNonce"`
// User that controls the Addresses
Username string `json:"username"`
Password string `json:"password"`
}
// CreateImportTx returns an unsigned transaction to import funds.
// The unsigned transaction must be signed with the key of [args.Payer]
func (service *Service) CreateImportTx(_ *http.Request, args *CreateImportTxArgs, response *SignResponse) error {
service.vm.Ctx.Log.Debug("platform.createImportTx called")
// Get the key of the Signer
db, err := service.vm.Ctx.Keystore.GetDatabase(args.Username, args.Password)
if err != nil {
return fmt.Errorf("couldn't get data for user '%s'. Does user exist?", args.Username)
}
user := user{db: db}
kc := secp256k1fx.NewKeychain()
for _, addr := range args.ImportAddresses {
key, err := user.getKey(addr)
if err != nil {
return errDB
}
kc.Add(key)
}
key, err := user.getKey(args.AccountID)
if err != nil {
return errDB
}
kc.Add(key)
addrs := ids.Set{}
for _, addr := range args.ImportAddresses {
addrs.Add(ids.NewID(hashing.ComputeHash256Array(addr.Bytes())))
}
utxos, err := service.vm.GetAtomicUTXOs(addrs)
if err != nil {
return fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err)
}
amount := uint64(0)
time := service.vm.clock.Unix()
ins := []*ava.TransferableInput{}
keys := [][]*crypto.PrivateKeySECP256K1R{}
for _, utxo := range utxos {
if !utxo.AssetID().Equals(service.vm.ava) {
continue
}
inputIntf, signers, err := kc.Spend(utxo.Out, time)
if err != nil {
continue
}
input, ok := inputIntf.(ava.Transferable)
if !ok {
continue
}
spent, err := math.Add64(amount, input.Amount())
if err != nil {
return err
}
amount = spent
in := &ava.TransferableInput{
UTXOID: utxo.UTXOID,
Asset: ava.Asset{ID: service.vm.ava},
In: input,
}
ins = append(ins, in)
keys = append(keys, signers)
}
ava.SortTransferableInputsWithSigners(ins, keys)
// Create the transaction
tx := ImportTx{UnsignedImportTx: UnsignedImportTx{
NetworkID: service.vm.Ctx.NetworkID,
Nonce: uint64(args.PayerNonce),
Account: args.AccountID,
Ins: ins,
}}
// TODO: Should we check if tx is already signed?
unsignedIntf := interface{}(&tx.UnsignedImportTx)
unsignedTxBytes, err := Codec.Marshal(&unsignedIntf)
if err != nil {
return fmt.Errorf("error serializing unsigned tx: %w", err)
}
hash := hashing.ComputeHash256(unsignedTxBytes)
sig, err := key.SignHash(hash)
if err != nil {
return errors.New("error while signing")
}
copy(tx.Sig[:], sig)
for _, credKeys := range keys {
cred := &secp256k1fx.Credential{}
for _, key := range credKeys {
sig, err := key.SignHash(hash)
if err != nil {
return fmt.Errorf("problem creating transaction: %w", err)
}
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
cred.Sigs = append(cred.Sigs, fixedSig)
}
tx.Creds = append(tx.Creds, cred)
}
txBytes, err := Codec.Marshal(genericTx{Tx: &tx})
if err != nil {
return errCreatingTransaction
}
response.Tx.Bytes = txBytes
return nil
}
// IssueTxArgs are the arguments to IssueTx
type IssueTxArgs struct {
// Tx being sent to the network
@ -800,69 +1028,25 @@ func (service *Service) IssueTx(_ *http.Request, args *IssueTxArgs, response *Is
return fmt.Errorf("error initializing tx: %s", err)
}
service.vm.unissuedEvents.Push(tx)
defer service.vm.resetTimer()
response.TxID = tx.ID()
return nil
case *CreateSubnetTx:
case DecisionTx:
if err := tx.initialize(service.vm); err != nil {
return fmt.Errorf("error initializing tx: %s", err)
}
service.vm.unissuedDecisionTxs = append(service.vm.unissuedDecisionTxs, tx)
defer service.vm.resetTimer()
response.TxID = tx.ID
return nil
response.TxID = tx.ID()
case AtomicTx:
if err := tx.initialize(service.vm); err != nil {
return fmt.Errorf("error initializing tx: %s", err)
}
service.vm.unissuedAtomicTxs = append(service.vm.unissuedAtomicTxs, tx)
response.TxID = tx.ID()
default:
return errors.New("Could not parse given tx. Must be one of: addDefaultSubnetValidatorTx, addDefaultSubnetDelegatorTx, addNonDefaultSubnetValidatorTx, createSubnetTx")
}
}
/*
******************************************************
**************** Create a Subnet *********************
******************************************************
*/
// CreateSubnetArgs are the arguments to CreateSubnet
type CreateSubnetArgs struct {
// The ID member of APISubnet is ignored
APISubnet
// Nonce of the account that pays the transaction fee
PayerNonce json.Uint64 `json:"payerNonce"`
}
// CreateSubnetResponse is the response from a call to CreateSubnet
type CreateSubnetResponse struct {
// Byte representation of the unsigned transaction to create a new subnet
UnsignedTx formatting.CB58 `json:"unsignedTx"`
}
// CreateSubnet returns an unsigned transaction to create a new subnet.
// The unsigned transaction must be signed with the key of [args.Payer]
func (service *Service) CreateSubnet(_ *http.Request, args *CreateSubnetArgs, response *CreateSubnetResponse) error {
service.vm.Ctx.Log.Debug("platform.createSubnet called")
// Create the transaction
tx := CreateSubnetTx{
UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{
NetworkID: service.vm.Ctx.NetworkID,
Nonce: uint64(args.PayerNonce),
ControlKeys: args.ControlKeys,
Threshold: uint16(args.Threshold),
},
key: nil,
Sig: [65]byte{},
bytes: nil,
return errors.New("Could not parse given tx. Must be a TimedTx, DecisionTx, or AtomicTx")
}
txBytes, err := Codec.Marshal(genericTx{Tx: &tx})
if err != nil {
return errCreatingTransaction
}
response.UnsignedTx.Bytes = txBytes
service.vm.resetTimer()
return nil
}
/*
@ -902,14 +1086,14 @@ type CreateBlockchainReply struct {
// CreateBlockchain issues a transaction to the network to create a new blockchain
func (service *Service) CreateBlockchain(_ *http.Request, args *CreateBlockchainArgs, reply *CreateBlockchainReply) error {
vmID, err := service.vm.ChainManager.LookupVM(args.VMID)
vmID, err := service.vm.chainManager.LookupVM(args.VMID)
if err != nil {
return fmt.Errorf("no VM with ID '%s' found", args.VMID)
}
fxIDs := []ids.ID(nil)
for _, fxIDStr := range args.FxIDs {
fxID, err := service.vm.ChainManager.LookupVM(fxIDStr)
fxID, err := service.vm.chainManager.LookupVM(fxIDStr)
if err != nil {
return fmt.Errorf("no FX with ID '%s' found", fxIDStr)
}
@ -974,7 +1158,7 @@ type GetBlockchainStatusReply struct {
// GetBlockchainStatus gets the status of a blockchain with the ID [args.BlockchainID].
func (service *Service) GetBlockchainStatus(_ *http.Request, args *GetBlockchainStatusArgs, reply *GetBlockchainStatusReply) error {
_, err := service.vm.ChainManager.Lookup(args.BlockchainID)
_, err := service.vm.chainManager.Lookup(args.BlockchainID)
if err == nil {
reply.Status = Validating
return nil

View File

@ -12,6 +12,8 @@ import (
// DecisionTx is an operation that can be decided without being proposed
type DecisionTx interface {
ID() ids.ID
initialize(vm *VM) error
// Attempt to verify this transaction with the provided state. The provided
@ -47,9 +49,10 @@ func (sb *StandardBlock) initialize(vm *VM, bytes []byte) error {
//
// This function also sets onAcceptDB database if the verification passes.
func (sb *StandardBlock) Verify() error {
parentBlock := sb.parentBlock()
// StandardBlock is not a modifier on a proposal block, so its parent must
// be a decision.
parent, ok := sb.parentBlock().(decision)
parent, ok := parentBlock.(decision)
if !ok {
return errInvalidBlockType
}

View File

@ -211,18 +211,18 @@ func (vm *VM) getSubnets(db database.Database) ([]*CreateSubnetTx, error) {
}
// get the subnet with the specified ID
func (vm *VM) getSubnet(db database.Database, ID ids.ID) (*CreateSubnetTx, error) {
func (vm *VM) getSubnet(db database.Database, id ids.ID) (*CreateSubnetTx, error) {
subnets, err := vm.getSubnets(db)
if err != nil {
return nil, err
}
for _, subnet := range subnets {
if subnet.ID.Equals(ID) {
if subnet.id.Equals(id) {
return subnet, nil
}
}
return nil, fmt.Errorf("couldn't find subnet with ID %s", ID)
return nil, fmt.Errorf("couldn't find subnet with ID %s", id)
}
// register each type that we'll be storing in the database

View File

@ -20,7 +20,7 @@ func TestBuildGenesis(t *testing.T) {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x5b, 0xcd, 0x15,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x05, 0x01, 0x5c, 0xce, 0x6c, 0x55, 0xd6, 0xb5,
0x0b, 0x01, 0x5c, 0xce, 0x6c, 0x55, 0xd6, 0xb5,
0x09, 0x84, 0x5c, 0x8c, 0x4e, 0x30, 0xbe, 0xd9,
0x8d, 0x39, 0x1a, 0xe7, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x3a, 0xde, 0x68, 0xb1, 0x00, 0x00, 0x00,

View File

@ -24,8 +24,10 @@ import (
"github.com/ava-labs/gecko/utils/timer"
"github.com/ava-labs/gecko/utils/units"
"github.com/ava-labs/gecko/utils/wrappers"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/core"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
const (
@ -108,6 +110,13 @@ func init() {
Codec.RegisterType(&Abort{}),
Codec.RegisterType(&Commit{}),
Codec.RegisterType(&StandardBlock{}),
Codec.RegisterType(&AtomicBlock{}),
Codec.RegisterType(&secp256k1fx.MintOutput{}),
Codec.RegisterType(&secp256k1fx.TransferOutput{}),
Codec.RegisterType(&secp256k1fx.MintInput{}),
Codec.RegisterType(&secp256k1fx.TransferInput{}),
Codec.RegisterType(&secp256k1fx.Credential{}),
Codec.RegisterType(&UnsignedAddDefaultSubnetValidatorTx{}),
Codec.RegisterType(&addDefaultSubnetValidatorTx{}),
@ -124,6 +133,12 @@ func init() {
Codec.RegisterType(&UnsignedCreateSubnetTx{}),
Codec.RegisterType(&CreateSubnetTx{}),
Codec.RegisterType(&UnsignedImportTx{}),
Codec.RegisterType(&ImportTx{}),
Codec.RegisterType(&UnsignedExportTx{}),
Codec.RegisterType(&ExportTx{}),
Codec.RegisterType(&advanceTimeTx{}),
Codec.RegisterType(&rewardValidatorTx{}),
)
@ -136,10 +151,19 @@ func init() {
type VM struct {
*core.SnowmanVM
Validators validators.Manager
validators validators.Manager
// The node's chain manager
ChainManager chains.Manager
chainManager chains.Manager
// AVA asset ID
ava ids.ID
// AVM is the ID of the ava virtual machine
avm ids.ID
fx secp256k1fx.Fx
codec codec.Codec
// Used to create and use keys.
factory crypto.FactorySECP256K1R
@ -154,6 +178,7 @@ type VM struct {
// Transactions that have not been put into blocks yet
unissuedEvents *EventHeap
unissuedDecisionTxs []DecisionTx
unissuedAtomicTxs []AtomicTx
// This timer goes off when it is time for the next validator to add/leave the validator set
// When it goes off resetTimer() is called, triggering creation of a new block
@ -181,6 +206,12 @@ func (vm *VM) Initialize(
return err
}
vm.codec = codec.NewDefault()
if err := vm.fx.Initialize(vm); err != nil {
return err
}
vm.codec = Codec
// Register this VM's types with the database so we can get/put structs to/from it
vm.registerDBTypes()
@ -298,7 +329,7 @@ func (vm *VM) initBlockchains() error {
for _, fxID := range chain.FxIDs {
chainParams.FxAliases = append(chainParams.FxAliases, fxID.String())
}
vm.ChainManager.CreateChain(chainParams)
vm.chainManager.CreateChain(chainParams)
}
return nil
}
@ -338,6 +369,24 @@ func (vm *VM) BuildBlock() (snowman.Block, error) {
return blk, vm.DB.Commit()
}
// If there is a pending atomic tx, build a block with it
if len(vm.unissuedAtomicTxs) > 0 {
tx := vm.unissuedAtomicTxs[0]
vm.unissuedAtomicTxs = vm.unissuedAtomicTxs[1:]
blk, err := vm.newAtomicBlock(preferredID, tx)
if err != nil {
return nil, err
}
if err := blk.Verify(); err != nil {
vm.resetTimer()
return nil, err
}
if err := vm.State.PutBlock(vm.DB, blk); err != nil {
return nil, err
}
return blk, vm.DB.Commit()
}
// Get the preferred block (which we want to build off)
preferred, err := vm.getBlock(preferredID)
vm.Ctx.Log.AssertNoError(err)
@ -502,9 +551,9 @@ func (vm *VM) CreateStaticHandlers() map[string]*common.HTTPHandler {
// Check if there is a block ready to be added to consensus
// If so, notify the consensus engine
func (vm *VM) resetTimer() {
// If there is a pending CreateChainTx, trigger building of a block
// with that transaction
if len(vm.unissuedDecisionTxs) > 0 {
// If there is a pending transaction, trigger building of a block with that
// transaction
if len(vm.unissuedDecisionTxs) > 0 || len(vm.unissuedAtomicTxs) > 0 {
vm.SnowmanVM.NotifyBlockReady()
return
}
@ -584,7 +633,7 @@ func (vm *VM) nextValidatorChangeTime(db database.Database, start bool) time.Tim
return earliest
}
for _, subnet := range subnets {
t := vm.nextSubnetValidatorChangeTime(db, subnet.ID, start)
t := vm.nextSubnetValidatorChangeTime(db, subnet.id, start)
if t.Before(earliest) {
earliest = t
}
@ -672,7 +721,7 @@ func (vm *VM) getValidators(validatorEvents *EventHeap) []validators.Validator {
}
func (vm *VM) updateValidators(subnetID ids.ID) error {
validatorSet, ok := vm.Validators.GetValidatorSet(subnetID)
validatorSet, ok := vm.validators.GetValidatorSet(subnetID)
if !ok {
return fmt.Errorf("couldn't get the validator sampler of the %s subnet", subnetID)
}
@ -686,3 +735,34 @@ func (vm *VM) updateValidators(subnetID ids.ID) error {
validatorSet.Set(validators)
return nil
}
// Codec ...
func (vm *VM) Codec() codec.Codec { return vm.codec }
// Clock ...
func (vm *VM) Clock() *timer.Clock { return &vm.clock }
// GetAtomicUTXOs returns the utxos that at least one of the provided addresses is
// referenced in.
func (vm *VM) GetAtomicUTXOs(addrs ids.Set) ([]*ava.UTXO, error) {
smDB := vm.Ctx.SharedMemory.GetDatabase(vm.avm)
defer vm.Ctx.SharedMemory.ReleaseDatabase(vm.avm)
state := ava.NewPrefixedState(smDB, vm.codec)
utxoIDs := ids.Set{}
for _, addr := range addrs.List() {
utxos, _ := state.AVMFunds(addr)
utxoIDs.Add(utxos...)
}
utxos := []*ava.UTXO{}
for _, utxoID := range utxoIDs.List() {
utxo, err := state.AVMUTXO(utxoID)
if err != nil {
return nil, err
}
utxos = append(utxos, utxo)
}
return utxos, nil
}

View File

@ -10,6 +10,7 @@ import (
"testing"
"time"
"github.com/ava-labs/gecko/chains/atomic"
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
@ -18,7 +19,10 @@ import (
"github.com/ava-labs/gecko/snow/validators"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/core"
"github.com/ava-labs/gecko/vms/secp256k1fx"
"github.com/ava-labs/gecko/vms/timestampvm"
)
@ -116,8 +120,8 @@ func defaultVM() *VM {
}
defaultSubnet := validators.NewSet()
vm.Validators = validators.NewManager()
vm.Validators.PutValidatorSet(DefaultSubnetID, defaultSubnet)
vm.validators = validators.NewManager()
vm.validators.PutValidatorSet(DefaultSubnetID, defaultSubnet)
vm.clock.Set(defaultGenesisTime)
db := memdb.New()
@ -149,7 +153,7 @@ func defaultVM() *VM {
&EventHeap{
SortByStartTime: false,
},
tx.ID,
tx.id,
)
if err != nil {
panic(err)
@ -159,7 +163,7 @@ func defaultVM() *VM {
&EventHeap{
SortByStartTime: true,
},
tx.ID,
tx.id,
)
if err != nil {
panic(err)
@ -433,7 +437,7 @@ func TestAddNonDefaultSubnetValidatorAccept(t *testing.T) {
uint64(startTime.Unix()),
uint64(endTime.Unix()),
keys[0].PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]},
keys[0],
@ -478,7 +482,7 @@ func TestAddNonDefaultSubnetValidatorAccept(t *testing.T) {
commit.Accept() // accept the proposal
// Verify that new validator is in pending validator set
pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.ID)
pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.id)
if err != nil {
t.Fatal(err)
}
@ -506,7 +510,7 @@ func TestAddNonDefaultSubnetValidatorReject(t *testing.T) {
uint64(startTime.Unix()),
uint64(endTime.Unix()),
keys[0].PublicKey().Address(),
testSubnet1.ID,
testSubnet1.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[1], testSubnet1ControlKeys[2]},
keys[0],
@ -551,7 +555,7 @@ func TestAddNonDefaultSubnetValidatorReject(t *testing.T) {
abort.Accept() // reject the proposal
// Verify that new validator NOT in pending validator set
pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.ID)
pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.id)
if err != nil {
t.Fatal(err)
}
@ -881,7 +885,7 @@ func TestCreateSubnet(t *testing.T) {
uint64(startTime.Unix()),
uint64(endTime.Unix()),
keys[0].PublicKey().Address(),
createSubnetTx.ID,
createSubnetTx.id,
testNetworkID,
[]*crypto.PrivateKeySECP256K1R{keys[0]},
keys[0],
@ -931,7 +935,7 @@ func TestCreateSubnet(t *testing.T) {
commit.Accept() // add the validator to pending validator set
// Verify validator is in pending validator set
pendingValidators, err := vm.getPendingValidators(vm.DB, createSubnetTx.ID)
pendingValidators, err := vm.getPendingValidators(vm.DB, createSubnetTx.id)
if err != nil {
t.Fatal(err)
}
@ -985,7 +989,7 @@ func TestCreateSubnet(t *testing.T) {
// Verify validator no longer in pending validator set
// Verify validator is in pending validator set
pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.ID)
pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.id)
if err != nil {
t.Fatal(err)
}
@ -994,7 +998,7 @@ func TestCreateSubnet(t *testing.T) {
}
// Verify validator is in current validator set
currentValidators, err := vm.getCurrentValidators(vm.DB, createSubnetTx.ID)
currentValidators, err := vm.getCurrentValidators(vm.DB, createSubnetTx.id)
if err != nil {
t.Fatal(err)
}
@ -1044,19 +1048,177 @@ func TestCreateSubnet(t *testing.T) {
commit.Accept() // remove validator from current validator set
// pending validators and current validator should be empty
pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.ID)
pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.id)
if err != nil {
t.Fatal(err)
}
if pendingValidators.Len() != 0 {
t.Fatal("pending validator set should be empty")
}
currentValidators, err = vm.getCurrentValidators(vm.DB, createSubnetTx.ID)
currentValidators, err = vm.getCurrentValidators(vm.DB, createSubnetTx.id)
if err != nil {
t.Fatal(err)
}
if currentValidators.Len() != 0 {
t.Fatal("pending validator set should be empty")
}
}
// test asset import
func TestAtomicImport(t *testing.T) {
vm := defaultVM()
avmID := ids.Empty.Prefix(0)
utxoID := ava.UTXOID{
TxID: ids.Empty.Prefix(1),
OutputIndex: 1,
}
assetID := ids.Empty.Prefix(2)
amount := uint64(50000)
key := keys[0]
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
vm.Ctx.SharedMemory = sm.NewBlockchainSharedMemory(vm.Ctx.ChainID)
tx, err := vm.newImportTx(
defaultNonce+1,
testNetworkID,
[]*ava.TransferableInput{&ava.TransferableInput{
UTXOID: utxoID,
Asset: ava.Asset{ID: assetID},
In: &secp256k1fx.TransferInput{
Amt: amount,
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
},
}},
[][]*crypto.PrivateKeySECP256K1R{[]*crypto.PrivateKeySECP256K1R{key}},
key,
)
if err != nil {
t.Fatal(err)
}
vm.Ctx.Lock.Lock()
defer vm.Ctx.Lock.Unlock()
vm.ava = assetID
vm.avm = avmID
vm.unissuedAtomicTxs = append(vm.unissuedAtomicTxs, tx)
if _, err := vm.BuildBlock(); err == nil {
t.Fatalf("should have errored due to missing utxos")
}
// Provide the avm UTXO:
smDB := vm.Ctx.SharedMemory.GetDatabase(avmID)
utxo := &ava.UTXO{
UTXOID: utxoID,
Asset: ava.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{
Amt: amount,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{key.PublicKey().Address()},
},
},
}
state := ava.NewPrefixedState(smDB, Codec)
if err := state.FundAVMUTXO(utxo); err != nil {
t.Fatal(err)
}
vm.Ctx.SharedMemory.ReleaseDatabase(avmID)
vm.unissuedAtomicTxs = append(vm.unissuedAtomicTxs, tx)
blk, err := vm.BuildBlock()
if err != nil {
t.Fatal(err)
}
if err := blk.Verify(); err != nil {
t.Fatal(err)
}
blk.Accept()
smDB = vm.Ctx.SharedMemory.GetDatabase(avmID)
defer vm.Ctx.SharedMemory.ReleaseDatabase(avmID)
state = ava.NewPrefixedState(smDB, vm.codec)
if _, err := state.AVMUTXO(utxoID.InputID()); err == nil {
t.Fatalf("shouldn't have been able to read the utxo")
}
}
// test optimistic asset import
func TestOptimisticAtomicImport(t *testing.T) {
vm := defaultVM()
avmID := ids.Empty.Prefix(0)
utxoID := ava.UTXOID{
TxID: ids.Empty.Prefix(1),
OutputIndex: 1,
}
assetID := ids.Empty.Prefix(2)
amount := uint64(50000)
key := keys[0]
sm := &atomic.SharedMemory{}
sm.Initialize(logging.NoLog{}, memdb.New())
vm.Ctx.SharedMemory = sm.NewBlockchainSharedMemory(vm.Ctx.ChainID)
tx, err := vm.newImportTx(
defaultNonce+1,
testNetworkID,
[]*ava.TransferableInput{&ava.TransferableInput{
UTXOID: utxoID,
Asset: ava.Asset{ID: assetID},
In: &secp256k1fx.TransferInput{
Amt: amount,
Input: secp256k1fx.Input{SigIndices: []uint32{0}},
},
}},
[][]*crypto.PrivateKeySECP256K1R{[]*crypto.PrivateKeySECP256K1R{key}},
key,
)
if err != nil {
t.Fatal(err)
}
vm.Ctx.Lock.Lock()
defer vm.Ctx.Lock.Unlock()
vm.ava = assetID
vm.avm = avmID
blk, err := vm.newAtomicBlock(vm.Preferred(), tx)
if err != nil {
t.Fatal(err)
}
if err := blk.Verify(); err == nil {
t.Fatalf("should have errored due to an invalid atomic utxo")
}
previousAccount, err := vm.getAccount(vm.DB, key.PublicKey().Address())
if err != nil {
t.Fatal(err)
}
blk.Accept()
newAccount, err := vm.getAccount(vm.DB, key.PublicKey().Address())
if err != nil {
t.Fatal(err)
}
if newAccount.Balance != previousAccount.Balance+amount {
t.Fatalf("failed to provide funds")
}
}

View File

@ -8,7 +8,7 @@ import (
"strings"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/components/ava"
)
// UTXOSet ...
@ -19,11 +19,11 @@ type UTXOSet struct {
// List of UTXOs in this set
// This can be used to iterate over. It should not be modified externally.
UTXOs []*avm.UTXO
UTXOs []*ava.UTXO
}
// Put ...
func (us *UTXOSet) Put(utxo *avm.UTXO) {
func (us *UTXOSet) Put(utxo *ava.UTXO) {
if us.utxoMap == nil {
us.utxoMap = make(map[[32]byte]int)
}
@ -36,7 +36,7 @@ func (us *UTXOSet) Put(utxo *avm.UTXO) {
}
// Get ...
func (us *UTXOSet) Get(id ids.ID) *avm.UTXO {
func (us *UTXOSet) Get(id ids.ID) *ava.UTXO {
if us.utxoMap == nil {
return nil
}
@ -48,7 +48,7 @@ func (us *UTXOSet) Get(id ids.ID) *avm.UTXO {
}
// Remove ...
func (us *UTXOSet) Remove(id ids.ID) *avm.UTXO {
func (us *UTXOSet) Remove(id ids.ID) *ava.UTXO {
i, ok := us.utxoMap[id.Key()]
if !ok {
return nil

View File

@ -18,6 +18,7 @@ import (
"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/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
@ -49,6 +50,8 @@ func NewWallet(log logging.Logger, networkID uint32, chainID ids.ID, txFee uint6
c.RegisterType(&avm.BaseTx{}),
c.RegisterType(&avm.CreateAssetTx{}),
c.RegisterType(&avm.OperationTx{}),
c.RegisterType(&avm.ImportTx{}),
c.RegisterType(&avm.ExportTx{}),
c.RegisterType(&secp256k1fx.MintOutput{}),
c.RegisterType(&secp256k1fx.TransferOutput{}),
c.RegisterType(&secp256k1fx.MintInput{}),
@ -92,8 +95,8 @@ 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)
func (w *Wallet) AddUTXO(utxo *ava.UTXO) {
out, ok := utxo.Out.(ava.Transferable)
if !ok {
return
}
@ -113,7 +116,7 @@ func (w *Wallet) RemoveUTXO(utxoID ids.ID) {
assetID := utxo.AssetID()
assetKey := assetID.Key()
newBalance := w.balance[assetKey] - utxo.Out.(avm.FxTransferable).Amount()
newBalance := w.balance[assetKey] - utxo.Out.(ava.Transferable).Amount()
if newBalance == 0 {
delete(w.balance, assetKey)
} else {
@ -135,7 +138,7 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) (
amountSpent := uint64(0)
time := w.clock.Unix()
ins := []*avm.TransferableInput{}
ins := []*ava.TransferableInput{}
keys := [][]*crypto.PrivateKeySECP256K1R{}
for _, utxo := range w.utxoSet.UTXOs {
if !utxo.AssetID().Equals(assetID) {
@ -145,7 +148,7 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) (
if err != nil {
continue
}
input, ok := inputIntf.(avm.FxTransferable)
input, ok := inputIntf.(ava.Transferable)
if !ok {
continue
}
@ -155,9 +158,9 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) (
}
amountSpent = spent
in := &avm.TransferableInput{
in := &ava.TransferableInput{
UTXOID: utxo.UTXOID,
Asset: avm.Asset{ID: assetID},
Asset: ava.Asset{ID: assetID},
In: input,
}
@ -173,11 +176,10 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) (
return nil, errors.New("insufficient funds")
}
avm.SortTransferableInputsWithSigners(ins, keys)
ava.SortTransferableInputsWithSigners(ins, keys)
outs := []*avm.TransferableOutput{
&avm.TransferableOutput{
Asset: avm.Asset{ID: assetID},
outs := []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{
Amt: amount,
Locktime: 0,
@ -186,17 +188,15 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) (
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},
outs = append(outs, &ava.TransferableOutput{
Asset: ava.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{
Amt: amountSpent - amount,
Locktime: 0,
@ -205,11 +205,10 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) (
Addrs: []ids.ShortID{changeAddr},
},
},
},
)
})
}
avm.SortTransferableOutputs(outs, w.codec)
ava.SortTransferableOutputs(outs, w.codec)
tx := &avm.Tx{
UnsignedTx: &avm.BaseTx{
@ -238,7 +237,7 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) (
cred.Sigs = append(cred.Sigs, fixedSig)
}
tx.Creds = append(tx.Creds, &avm.Credential{Cred: cred})
tx.Creds = append(tx.Creds, cred)
}
b, err := w.codec.Marshal(tx)

View File

@ -14,6 +14,7 @@ import (
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/utils/units"
"github.com/ava-labs/gecko/vms/avm"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/secp256k1fx"
)
@ -83,9 +84,9 @@ func TestWalletAddUTXO(t *testing.T) {
t.Fatal(err)
}
utxo := &avm.UTXO{
UTXOID: avm.UTXOID{TxID: ids.Empty.Prefix(0)},
Asset: avm.Asset{ID: ids.Empty.Prefix(1)},
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{TxID: ids.Empty.Prefix(0)},
Asset: ava.Asset{ID: ids.Empty.Prefix(1)},
Out: &secp256k1fx.TransferOutput{
Amt: 1000,
},
@ -105,9 +106,9 @@ func TestWalletAddInvalidUTXO(t *testing.T) {
t.Fatal(err)
}
utxo := &avm.UTXO{
UTXOID: avm.UTXOID{TxID: ids.Empty.Prefix(0)},
Asset: avm.Asset{ID: ids.Empty.Prefix(1)},
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{TxID: ids.Empty.Prefix(0)},
Asset: ava.Asset{ID: ids.Empty.Prefix(1)},
}
w.AddUTXO(utxo)
@ -130,9 +131,9 @@ func TestWalletCreateTx(t *testing.T) {
if err != nil {
t.Fatal(err)
}
utxo := &avm.UTXO{
UTXOID: avm.UTXOID{TxID: ids.Empty.Prefix(1)},
Asset: avm.Asset{ID: assetID},
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{TxID: ids.Empty.Prefix(1)},
Asset: ava.Asset{ID: assetID},
Out: &secp256k1fx.TransferOutput{
Amt: 1000,
OutputOwners: secp256k1fx.OutputOwners{