mirror of https://github.com/poanetwork/gecko.git
commit
c45fa2b109
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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})
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
@ -110,6 +111,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
|
||||
|
@ -137,6 +139,7 @@ func New(
|
|||
awaiter Awaiter,
|
||||
server *api.Server,
|
||||
keystore *keystore.Keystore,
|
||||
sharedMemory *atomic.SharedMemory,
|
||||
) Manager {
|
||||
timeoutManager := timeout.Manager{}
|
||||
timeoutManager.Initialize(requestTimeout)
|
||||
|
@ -162,6 +165,7 @@ func New(
|
|||
awaiter: awaiter,
|
||||
server: server,
|
||||
keystore: keystore,
|
||||
sharedMemory: sharedMemory,
|
||||
}
|
||||
m.Initialize()
|
||||
return m
|
||||
|
@ -249,6 +253,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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
return nil
|
||||
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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/ava-labs/gecko/utils/json"
|
||||
"github.com/ava-labs/gecko/utils/units"
|
||||
"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"
|
||||
|
@ -402,3 +403,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()
|
||||
}
|
||||
|
|
24
node/node.go
24
node/node.go
|
@ -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{})
|
||||
|
@ -364,6 +371,8 @@ func (n *Node) initChains() {
|
|||
ChainManager: n.chainManager,
|
||||
Validators: vdrs,
|
||||
StakingEnabled: n.Config.EnableStaking,
|
||||
AVA: genesis.AVAAssetID(n.Config.NetworkID),
|
||||
AVM: genesis.VMGenesis(n.Config.NetworkID, avm.ID).ID(),
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -423,12 +432,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")
|
||||
|
@ -510,6 +527,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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,14 +45,24 @@ 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.vtxBlocked.Abandon(vtxID)
|
||||
return
|
||||
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)
|
||||
|
||||
i.t.Consensus.Add(i.vtx)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
@ -2364,3 +2376,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)
|
||||
}
|
||||
|
|
|
@ -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.Accept()
|
||||
t.numAccepted.Inc()
|
||||
} else {
|
||||
t.numDropped.Inc()
|
||||
}
|
||||
t.tx.Verify()
|
||||
t.tx.Accept()
|
||||
t.numAccepted.Inc()
|
||||
}
|
||||
}
|
||||
func (t *txJob) Bytes() []byte { return t.tx.Bytes() }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.Accept()
|
||||
b.numAccepted.Inc()
|
||||
} else {
|
||||
b.numDropped.Inc()
|
||||
}
|
||||
b.blk.Verify()
|
||||
b.blk.Accept()
|
||||
b.numAccepted.Inc()
|
||||
}
|
||||
}
|
||||
func (b *blockJob) Bytes() []byte { return b.blk.Bytes() }
|
||||
|
|
|
@ -25,8 +25,9 @@ type Blk struct {
|
|||
parent snowman.Block
|
||||
id ids.ID
|
||||
|
||||
height int
|
||||
status choices.Status
|
||||
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
|
||||
|
|
|
@ -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())
|
||||
|
@ -419,6 +431,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
|
||||
}
|
||||
|
||||
|
@ -1078,3 +1101,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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 ...
|
||||
|
|
|
@ -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 ...
|
||||
|
|
|
@ -6,9 +6,10 @@ 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"
|
||||
)
|
||||
|
@ -28,31 +29,17 @@ var (
|
|||
|
||||
// BaseTx is the basis of all transactions.
|
||||
type BaseTx struct {
|
||||
metadata
|
||||
ava.Metadata
|
||||
|
||||
NetID uint32 `serialize:"true" json:"networkID"` // ID of the network this chain lives on
|
||||
BCID ids.ID `serialize:"true" json:"blockchainID"` // ID of the chain on which this transaction exists (prevents replay attacks)
|
||||
Outs []*TransferableOutput `serialize:"true" json:"outputs"` // The outputs of this transaction
|
||||
Ins []*TransferableInput `serialize:"true" json:"inputs"` // The inputs to this transaction
|
||||
NetID uint32 `serialize:"true" json:"networkID"` // ID of the network this chain lives on
|
||||
BCID ids.ID `serialize:"true" json:"blockchainID"` // ID of the chain on which this transaction exists (prevents replay attacks)
|
||||
Outs []*ava.TransferableOutput `serialize:"true" json:"outputs"` // The outputs of this transaction
|
||||
Ins []*ava.TransferableInput `serialize:"true" json:"inputs"` // 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)
|
||||
}
|
||||
|
@ -69,19 +56,17 @@ 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(),
|
||||
},
|
||||
Out: out.Out,
|
||||
Asset: ava.Asset{ID: out.AssetID()},
|
||||
Out: out.Out,
|
||||
}
|
||||
}
|
||||
return utxos
|
||||
|
@ -98,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
|
||||
}
|
||||
|
||||
|
@ -111,48 +98,19 @@ 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.
|
||||
|
@ -166,46 +124,11 @@ func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable
|
|||
}
|
||||
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)
|
||||
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) {
|
||||
|
@ -222,3 +145,6 @@ func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable
|
|||
}
|
||||
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
|
@ -10,8 +10,8 @@ import (
|
|||
"unicode"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -44,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,
|
||||
|
@ -111,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 []verify.Verifiable) error {
|
||||
return t.BaseTx.SemanticVerify(vm, uTx, creds)
|
||||
}
|
||||
|
||||
// Sort ...
|
||||
func (t *CreateAssetTx) Sort() { sortInitialStates(t.States) }
|
||||
|
|
|
@ -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,64 +93,60 @@ 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{
|
||||
ID: ids.NewID([32]byte{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
}),
|
||||
},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
Locktime: 54321,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{
|
||||
ids.NewShortID([20]byte{
|
||||
0x51, 0x02, 0x5c, 0x61, 0xfb, 0xcf, 0xc0, 0x78,
|
||||
0xf6, 0x93, 0x34, 0xf8, 0x34, 0xbe, 0x6d, 0xd2,
|
||||
0x6d, 0x55, 0xa9, 0x55,
|
||||
}),
|
||||
ids.NewShortID([20]byte{
|
||||
0xc3, 0x34, 0x41, 0x28, 0xe0, 0x60, 0x12, 0x8e,
|
||||
0xde, 0x35, 0x23, 0xa2, 0x4a, 0x46, 0x1c, 0x89,
|
||||
0x43, 0xab, 0x08, 0x59,
|
||||
}),
|
||||
},
|
||||
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,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
}),
|
||||
},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 12345,
|
||||
Locktime: 54321,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{
|
||||
ids.NewShortID([20]byte{
|
||||
0x51, 0x02, 0x5c, 0x61, 0xfb, 0xcf, 0xc0, 0x78,
|
||||
0xf6, 0x93, 0x34, 0xf8, 0x34, 0xbe, 0x6d, 0xd2,
|
||||
0x6d, 0x55, 0xa9, 0x55,
|
||||
}),
|
||||
ids.NewShortID([20]byte{
|
||||
0xc3, 0x34, 0x41, 0x28, 0xe0, 0x60, 0x12, 0x8e,
|
||||
0xde, 0x35, 0x23, 0xa2, 0x4a, 0x46, 0x1c, 0x89,
|
||||
0x43, 0xab, 0x08, 0x59,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Ins: []*TransferableInput{
|
||||
&TransferableInput{
|
||||
UTXOID: UTXOID{
|
||||
TxID: ids.NewID([32]byte{
|
||||
0xf1, 0xe1, 0xd1, 0xc1, 0xb1, 0xa1, 0x91, 0x81,
|
||||
0x71, 0x61, 0x51, 0x41, 0x31, 0x21, 0x11, 0x01,
|
||||
0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80,
|
||||
0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00,
|
||||
}),
|
||||
OutputIndex: 5,
|
||||
},
|
||||
Asset: Asset{
|
||||
ID: ids.NewID([32]byte{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
}),
|
||||
},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 123456789,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{3, 7},
|
||||
},
|
||||
}},
|
||||
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,
|
||||
0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80,
|
||||
0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00,
|
||||
}),
|
||||
OutputIndex: 5,
|
||||
},
|
||||
Asset: ava.Asset{
|
||||
ID: ids.NewID([32]byte{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
}),
|
||||
},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 123456789,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{3, 7},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
Name: "Volatility Index",
|
||||
Symbol: "VIX",
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"sort"
|
||||
|
||||
"github.com/ava-labs/gecko/utils"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/components/verify"
|
||||
)
|
||||
|
||||
|
@ -19,7 +20,7 @@ var (
|
|||
|
||||
// OperableInput ...
|
||||
type OperableInput struct {
|
||||
UTXOID `serialize:"true"`
|
||||
ava.UTXOID `serialize:"true"`
|
||||
|
||||
In verify.Verifiable `serialize:"true" json:"input"`
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
)
|
||||
|
||||
func TestOperableInputVerifyNil(t *testing.T) {
|
||||
|
@ -25,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)
|
||||
|
@ -41,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) {
|
||||
|
@ -95,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")
|
||||
|
|
|
@ -9,6 +9,7 @@ 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"
|
||||
)
|
||||
|
@ -20,7 +21,7 @@ var (
|
|||
|
||||
// Operation ...
|
||||
type Operation struct {
|
||||
Asset `serialize:"true"`
|
||||
ava.Asset `serialize:"true"`
|
||||
|
||||
Ins []*OperableInput `serialize:"true" json:"inputs"`
|
||||
Outs []verify.Verifiable `serialize:"true" json:"outputs"`
|
||||
|
|
|
@ -7,6 +7,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"
|
||||
)
|
||||
|
@ -22,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")
|
||||
|
@ -34,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{},
|
||||
},
|
||||
|
@ -49,23 +46,21 @@ func TestOperationVerifyInvalidInput(t *testing.T) {
|
|||
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{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -76,15 +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,
|
||||
},
|
||||
Asset: ava.Asset{ID: ids.Empty},
|
||||
Outs: []verify.Verifiable{
|
||||
&TestTransferable{Val: 1},
|
||||
&TestTransferable{Val: 0},
|
||||
&ava.TestTransferable{Val: 1},
|
||||
&ava.TestTransferable{Val: 0},
|
||||
},
|
||||
}
|
||||
if err := op.Verify(c); err == nil {
|
||||
|
@ -95,11 +88,9 @@ func TestOperationVerifyOutputsNotSorted(t *testing.T) {
|
|||
func TestOperationVerify(t *testing.T) {
|
||||
c := codec.NewDefault()
|
||||
op := &Operation{
|
||||
Asset: Asset{
|
||||
ID: ids.Empty,
|
||||
},
|
||||
Asset: ava.Asset{ID: ids.Empty},
|
||||
Outs: []verify.Verifiable{
|
||||
&testVerifiable{},
|
||||
&ava.TestVerifiable{},
|
||||
},
|
||||
}
|
||||
if err := op.Verify(c); err != nil {
|
||||
|
@ -109,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{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -149,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{},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -8,12 +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")
|
||||
)
|
||||
|
@ -29,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 {
|
||||
|
@ -49,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,
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Out: out,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -77,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 {
|
||||
|
@ -126,38 +128,11 @@ func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verif
|
|||
cred := creds[i+offset]
|
||||
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
|
||||
}
|
||||
|
||||
utxos = append(utxos, utxo.Out)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,40 +19,36 @@ 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{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
},
|
||||
Asset: Asset{
|
||||
ID: asset,
|
||||
},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 20 * units.KiloAva,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
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{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}}
|
||||
}},
|
||||
}}
|
||||
|
||||
unsignedBytes, err := vm.codec.Marshal(tx.UnsignedTx)
|
||||
if err != nil {
|
||||
|
@ -116,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 {
|
||||
|
@ -141,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},
|
||||
|
|
|
@ -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)
|
||||
|
@ -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,44 +637,36 @@ 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,
|
||||
},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: uint64(args.Amount),
|
||||
Locktime: 0,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{to},
|
||||
},
|
||||
outs := []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: uint64(args.Amount),
|
||||
Locktime: 0,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{to},
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
||||
if amountSpent > uint64(args.Amount) {
|
||||
changeAddr := kc.Keys[0].PublicKey().Address()
|
||||
outs = append(outs,
|
||||
&TransferableOutput{
|
||||
Asset: Asset{
|
||||
ID: assetID,
|
||||
},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: amountSpent - uint64(args.Amount),
|
||||
Locktime: 0,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{changeAddr},
|
||||
},
|
||||
outs = append(outs, &ava.TransferableOutput{
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: amountSpent - uint64(args.Amount),
|
||||
Locktime: 0,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{changeAddr},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
SortTransferableOutputs(outs, service.vm.codec)
|
||||
ava.SortTransferableOutputs(outs, service.vm.codec)
|
||||
|
||||
tx := Tx{
|
||||
UnsignedTx: &BaseTx{
|
||||
|
@ -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,
|
||||
|
@ -990,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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
149
vms/avm/state.go
149
vms/avm/state.go
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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,35 +245,31 @@ 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{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
},
|
||||
Asset: Asset{
|
||||
ID: asset,
|
||||
},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 20 * units.KiloAva,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
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{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}}
|
||||
}},
|
||||
}}
|
||||
|
||||
unsignedBytes, err := vm.codec.Marshal(tx.UnsignedTx)
|
||||
if err != nil {
|
||||
|
@ -312,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 {
|
||||
|
@ -339,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")
|
||||
|
|
|
@ -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{})
|
||||
|
|
|
@ -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},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ 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"
|
||||
)
|
||||
|
@ -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 []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.
|
||||
|
|
|
@ -8,6 +8,7 @@ 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"
|
||||
|
@ -43,35 +44,29 @@ 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{
|
||||
TxID: ids.Empty,
|
||||
OutputIndex: 0,
|
||||
},
|
||||
Asset: Asset{
|
||||
ID: asset,
|
||||
},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 20 * units.KiloAva,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
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{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
Creds: []verify.Verifiable{
|
||||
&testVerifiable{err: errUnneededAddress},
|
||||
}},
|
||||
},
|
||||
Creds: []verify.Verifiable{&ava.TestVerifiable{Err: errUnneededAddress}},
|
||||
}
|
||||
|
||||
b, err := c.Marshal(tx)
|
||||
|
@ -95,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{
|
||||
|
@ -119,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{
|
||||
|
@ -137,10 +128,10 @@ func TestTxInvalidUnsignedTx(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
Creds: []verify.Verifiable{
|
||||
&testVerifiable{},
|
||||
&testVerifiable{},
|
||||
&ava.TestVerifiable{},
|
||||
&ava.TestVerifiable{},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -165,53 +156,42 @@ 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,
|
||||
},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 20 * units.KiloAva,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
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{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
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: []verify.Verifiable{
|
||||
&testVerifiable{},
|
||||
},
|
||||
Creds: []verify.Verifiable{&ava.TestVerifiable{}},
|
||||
}
|
||||
|
||||
b, err := c.Marshal(tx)
|
||||
|
|
|
@ -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")
|
||||
|
@ -22,6 +24,7 @@ var (
|
|||
// performance boost
|
||||
type UniqueTx struct {
|
||||
*TxState
|
||||
|
||||
vm *VM
|
||||
txID ids.ID
|
||||
}
|
||||
|
@ -34,8 +37,8 @@ type TxState struct {
|
|||
validity error
|
||||
|
||||
inputs ids.Set
|
||||
inputUTXOs []*UTXOID
|
||||
utxos []*UTXO
|
||||
inputUTXOs []*ava.UTXOID
|
||||
utxos []*ava.UTXO
|
||||
deps []snowstorm.Tx
|
||||
|
||||
status choices.Status
|
||||
|
@ -97,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
|
||||
|
@ -119,12 +129,19 @@ 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.deps = nil // Needed to prevent a memory leak
|
||||
|
@ -136,6 +153,8 @@ func (tx *UniqueTx) Accept() {
|
|||
|
||||
// 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
|
||||
|
@ -172,6 +191,9 @@ func (tx *UniqueTx) Dependencies() []snowstorm.Tx {
|
|||
|
||||
txIDs := ids.Set{}
|
||||
for _, in := range tx.InputUTXOs() {
|
||||
if in.Symbolic() {
|
||||
continue
|
||||
}
|
||||
txID, _ := in.InputSource()
|
||||
if !txIDs.Contains(txID) {
|
||||
txIDs.Add(txID)
|
||||
|
@ -207,7 +229,7 @@ func (tx *UniqueTx) InputIDs() ids.Set {
|
|||
}
|
||||
|
||||
// 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.Tx == nil || len(tx.inputUTXOs) != 0 {
|
||||
return tx.inputUTXOs
|
||||
|
@ -217,7 +239,7 @@ func (tx *UniqueTx) InputUTXOs() []*UTXOID {
|
|||
}
|
||||
|
||||
// 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.Tx == nil || len(tx.utxos) != 0 {
|
||||
return tx.utxos
|
||||
|
@ -271,18 +293,18 @@ func (tx *UniqueTx) SemanticVerify() error {
|
|||
return tx.validity
|
||||
}
|
||||
|
||||
tx.verifiedState = true
|
||||
tx.validity = tx.Tx.SemanticVerify(tx.vm, tx)
|
||||
|
||||
if tx.validity == nil {
|
||||
tx.vm.pubsub.Publish("verified", tx.ID())
|
||||
if err := tx.Tx.SemanticVerify(tx.vm, tx); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.validity
|
||||
|
||||
tx.verifiedState = true
|
||||
tx.vm.pubsub.Publish("verified", tx.ID())
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsignedBytes returns the unsigned bytes of the transaction
|
||||
func (tx *UniqueTx) UnsignedBytes() []byte {
|
||||
b, err := tx.vm.codec.Marshal(&tx.Tx.UnsignedTx)
|
||||
b, err := tx.vm.codec.Marshal(&tx.UnsignedTx)
|
||||
tx.vm.ctx.Log.AssertNoError(err)
|
||||
return b
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -268,16 +275,41 @@ func (vm *VM) IssueTx(b []byte, onDecide func(choices.Status)) (ids.ID, error) {
|
|||
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 {
|
||||
|
@ -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]
|
||||
|
|
|
@ -15,6 +15,7 @@ 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"
|
||||
|
@ -51,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{})
|
||||
|
@ -206,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,
|
||||
|
@ -227,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,
|
||||
|
@ -248,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,
|
||||
|
@ -277,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,
|
||||
|
@ -298,9 +301,7 @@ func TestTxSerialization(t *testing.T) {
|
|||
},
|
||||
Ops: []*Operation{
|
||||
&Operation{
|
||||
Asset: Asset{
|
||||
ID: asset,
|
||||
},
|
||||
Asset: ava.Asset{ID: asset},
|
||||
Outs: []verify.Verifiable{
|
||||
&secp256k1fx.MintOutput{
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
|
@ -316,10 +317,8 @@ func TestTxSerialization(t *testing.T) {
|
|||
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{
|
||||
|
@ -334,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{})
|
||||
|
@ -440,29 +441,25 @@ 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{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
},
|
||||
Asset: Asset{
|
||||
ID: genesisTx.ID(),
|
||||
},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 50000,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
},
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 50000,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}}
|
||||
}},
|
||||
}}
|
||||
|
||||
unsignedBytes, err := vm.codec.Marshal(&newTx.UnsignedTx)
|
||||
if err != nil {
|
||||
|
@ -573,39 +570,35 @@ 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{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
},
|
||||
Asset: Asset{ID: genesisTx.ID()},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 50000,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: genesisTx.ID(),
|
||||
OutputIndex: 1,
|
||||
},
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 50000,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Outs: []*TransferableOutput{
|
||||
&TransferableOutput{
|
||||
Asset: Asset{ID: genesisTx.ID()},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 50000,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{key.PublicKey().Address()},
|
||||
},
|
||||
}},
|
||||
Outs: []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: 50000,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{key.PublicKey().Address()},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}}
|
||||
}},
|
||||
}}
|
||||
|
||||
unsignedBytes, err := vm.codec.Marshal(&firstTx.UnsignedTx)
|
||||
if err != nil {
|
||||
|
@ -636,27 +629,25 @@ 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{
|
||||
TxID: firstTx.ID(),
|
||||
OutputIndex: 0,
|
||||
},
|
||||
Asset: Asset{ID: genesisTx.ID()},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 50000,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
Ins: []*ava.TransferableInput{&ava.TransferableInput{
|
||||
UTXOID: ava.UTXOID{
|
||||
TxID: firstTx.ID(),
|
||||
OutputIndex: 0,
|
||||
},
|
||||
Asset: ava.Asset{ID: genesisTx.ID()},
|
||||
In: &secp256k1fx.TransferInput{
|
||||
Amt: 50000,
|
||||
Input: secp256k1fx.Input{
|
||||
SigIndices: []uint32{
|
||||
0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}}
|
||||
}},
|
||||
}}
|
||||
|
||||
unsignedBytes, err = vm.codec.Marshal(&secondTx.UnsignedTx)
|
||||
if err != nil {
|
||||
|
|
|
@ -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"
|
|
@ -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"
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 }
|
|
@ -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" json:"output"`
|
||||
Out Transferable `serialize:"true" json:"output"`
|
||||
}
|
||||
|
||||
// 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" json:"input"`
|
||||
In Transferable `serialize:"true" json:"input"`
|
||||
}
|
||||
|
||||
// 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})
|
||||
}
|
|
@ -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,34 +46,24 @@ func TestTransferableOutputSorting(t *testing.T) {
|
|||
|
||||
outs := []*TransferableOutput{
|
||||
&TransferableOutput{
|
||||
Asset: Asset{
|
||||
ID: ids.NewID([32]byte{1}),
|
||||
},
|
||||
Out: &TestTransferable{Val: 1},
|
||||
Asset: Asset{ID: ids.NewID([32]byte{1})},
|
||||
Out: &TestTransferable{Val: 1},
|
||||
},
|
||||
&TransferableOutput{
|
||||
Asset: Asset{
|
||||
ID: ids.Empty,
|
||||
},
|
||||
Out: &TestTransferable{Val: 1},
|
||||
Asset: Asset{ID: ids.Empty},
|
||||
Out: &TestTransferable{Val: 1},
|
||||
},
|
||||
&TransferableOutput{
|
||||
Asset: Asset{
|
||||
ID: ids.NewID([32]byte{1}),
|
||||
},
|
||||
Out: &TestTransferable{Val: 0},
|
||||
Asset: Asset{ID: ids.NewID([32]byte{1})},
|
||||
Out: &TestTransferable{Val: 0},
|
||||
},
|
||||
&TransferableOutput{
|
||||
Asset: Asset{
|
||||
ID: ids.Empty,
|
||||
},
|
||||
Out: &TestTransferable{Val: 0},
|
||||
Asset: Asset{ID: ids.Empty},
|
||||
Out: &TestTransferable{Val: 0},
|
||||
},
|
||||
&TransferableOutput{
|
||||
Asset: Asset{
|
||||
ID: ids.Empty,
|
||||
},
|
||||
Out: &TestTransferable{Val: 0},
|
||||
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")
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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" json:"txID"`
|
||||
OutputIndex uint32 `serialize:"true" json:"outputIndex"`
|
||||
|
||||
// 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 {
|
|
@ -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"
|
|
@ -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,
|
|
@ -325,9 +325,9 @@ func TestAddDefaultSubnetDelegatorTxSemanticVerify(t *testing.T) {
|
|||
}
|
||||
|
||||
tx, err = vm.newAddDefaultSubnetDelegatorTx(
|
||||
defaultNonce+1, // nonce
|
||||
defaultStakeAmount, // weight
|
||||
uint64(newTimestamp.Unix()), // start time
|
||||
defaultNonce+1, // nonce
|
||||
defaultStakeAmount, // weight
|
||||
uint64(newTimestamp.Unix()), // start time
|
||||
uint64(newTimestamp.Add(MinimumStakingDuration).Unix()), // end time
|
||||
defaultKey.PublicKey().Address(), // node ID
|
||||
defaultKey.PublicKey().Address(), // destination
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -25,9 +25,6 @@ var (
|
|||
|
||||
// UnsignedCreateSubnetTx is an unsigned proposal to create a new subnet
|
||||
type UnsignedCreateSubnetTx struct {
|
||||
// The VM this tx exists within
|
||||
vm *VM
|
||||
|
||||
// NetworkID is the ID of the network this tx was issued on
|
||||
NetworkID uint32 `serialize:"true"`
|
||||
|
||||
|
@ -55,6 +52,9 @@ type CreateSubnetTx struct {
|
|||
// [key] is non-nil iff this tx is valid
|
||||
key crypto.PublicKey
|
||||
|
||||
// The VM this tx exists within
|
||||
vm *VM
|
||||
|
||||
// ID is this transaction's ID
|
||||
id ids.ID
|
||||
|
||||
|
@ -135,7 +135,7 @@ func (tx *CreateSubnetTx) SemanticVerify(db database.Database) (func(), error) {
|
|||
|
||||
// Register new subnet in validator manager
|
||||
onAccept := func() {
|
||||
tx.vm.Validators.PutValidatorSet(tx.id, validators.NewSet())
|
||||
tx.vm.validators.PutValidatorSet(tx.id, validators.NewSet())
|
||||
}
|
||||
|
||||
return onAccept, nil
|
||||
|
@ -171,15 +171,12 @@ func (tx *CreateSubnetTx) initialize(vm *VM) error {
|
|||
func (vm *VM) newCreateSubnetTx(networkID uint32, nonce uint64, controlKeys []ids.ShortID,
|
||||
threshold uint16, payerKey *crypto.PrivateKeySECP256K1R,
|
||||
) (*CreateSubnetTx, error) {
|
||||
tx := &CreateSubnetTx{
|
||||
UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{
|
||||
vm: vm,
|
||||
NetworkID: networkID,
|
||||
Nonce: nonce,
|
||||
ControlKeys: controlKeys,
|
||||
Threshold: threshold,
|
||||
},
|
||||
}
|
||||
tx := &CreateSubnetTx{UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{
|
||||
NetworkID: networkID,
|
||||
Nonce: nonce,
|
||||
ControlKeys: controlKeys,
|
||||
Threshold: threshold,
|
||||
}}
|
||||
|
||||
if threshold == 0 && len(tx.ControlKeys) > 0 {
|
||||
return nil, errUnneededKeys
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 := from.Sign(unsignedBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(tx.Sig[:], sig)
|
||||
|
||||
return tx, tx.initialize(vm)
|
||||
}
|
|
@ -19,13 +19,17 @@ type Factory struct {
|
|||
ChainManager chains.Manager
|
||||
Validators validators.Manager
|
||||
StakingEnabled bool
|
||||
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,
|
||||
StakingEnabled: f.StakingEnabled,
|
||||
chainManager: f.ChainManager,
|
||||
validators: f.Validators,
|
||||
stakingEnabled: f.StakingEnabled,
|
||||
ava: f.AVA,
|
||||
avm: f.AVM,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: to.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 := to.SignHash(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(tx.Sig[:], sig)
|
||||
|
||||
return tx, tx.initialize(vm)
|
||||
}
|
|
@ -9,15 +9,16 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ava-labs/gecko/vms/secp256k1fx"
|
||||
|
||||
"github.com/ava-labs/gecko/vms/avm"
|
||||
|
||||
"github.com/ava-labs/gecko/database"
|
||||
"github.com/ava-labs/gecko/ids"
|
||||
"github.com/ava-labs/gecko/utils/crypto"
|
||||
"github.com/ava-labs/gecko/utils/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/avm"
|
||||
"github.com/ava-labs/gecko/vms/components/ava"
|
||||
"github.com/ava-labs/gecko/vms/secp256k1fx"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -235,7 +236,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)
|
||||
}
|
||||
|
@ -419,6 +420,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
|
||||
|
@ -427,15 +433,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("AddDefaultSubnetValidator called")
|
||||
|
||||
if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID
|
||||
|
@ -477,16 +477,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("AddDefaultSubnetDelegator called")
|
||||
|
||||
if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID
|
||||
|
@ -528,15 +522,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{
|
||||
|
@ -570,6 +558,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 **********************
|
||||
|
@ -631,6 +696,8 @@ func (service *Service) Sign(_ *http.Request, args *SignArgs, reply *SignRespons
|
|||
genTx.Tx, err = service.signCreateSubnetTx(tx, key)
|
||||
case *CreateChainTx:
|
||||
genTx.Tx, err = service.signCreateChainTx(tx, key)
|
||||
case *ExportTx:
|
||||
genTx.Tx, err = service.signExportTx(tx, key)
|
||||
default:
|
||||
err = errors.New("Could not parse given tx")
|
||||
}
|
||||
|
@ -711,6 +778,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
|
||||
|
@ -764,6 +854,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
|
||||
}
|
||||
|
||||
// Signs an unsigned or partially signed CreateChainTx 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
|
||||
|
@ -844,67 +1069,24 @@ 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 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
|
||||
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 either a TimedTx or a DecisionTx")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
******************************************************
|
||||
**************** 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("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
|
||||
}
|
||||
|
||||
|
@ -935,24 +1117,19 @@ type CreateBlockchainArgs struct {
|
|||
GenesisData formatting.CB58 `json:"genesisData"`
|
||||
}
|
||||
|
||||
// CreateBlockchainReply is the reply from calling CreateBlockchain
|
||||
type CreateBlockchainReply struct {
|
||||
UnsignedTx formatting.CB58 `json:"unsignedTx"`
|
||||
}
|
||||
|
||||
// CreateBlockchain returns an unsigned transaction to create a new blockchain
|
||||
// Must be signed with the Subnet's control keys and with a key that pays the transaction fee before issuance
|
||||
func (service *Service) CreateBlockchain(_ *http.Request, args *CreateBlockchainArgs, response *CreateBlockchainReply) error {
|
||||
func (service *Service) CreateBlockchain(_ *http.Request, args *CreateBlockchainArgs, response *CreateTxResponse) error {
|
||||
service.vm.Ctx.Log.Debug("createBlockchain called")
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -1014,7 +1191,7 @@ type GetBlockchainStatusReply struct {
|
|||
func (service *Service) GetBlockchainStatus(_ *http.Request, args *GetBlockchainStatusArgs, reply *GetBlockchainStatusReply) error {
|
||||
service.vm.Ctx.Log.Debug("getBlockchainStatus called")
|
||||
|
||||
_, err := service.vm.ChainManager.Lookup(args.BlockchainID)
|
||||
_, err := service.vm.chainManager.Lookup(args.BlockchainID)
|
||||
if err == nil {
|
||||
reply.Status = Validating
|
||||
return nil
|
||||
|
|
|
@ -49,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
|
||||
}
|
||||
|
|
|
@ -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{}),
|
||||
)
|
||||
|
@ -137,14 +152,23 @@ type VM struct {
|
|||
*core.SnowmanVM
|
||||
|
||||
// Node's validator manager
|
||||
// Maps Subnets --> nodes in the Subnet
|
||||
Validators validators.Manager
|
||||
// Maps Subnets --> nodes in the Subnet HEAD
|
||||
validators validators.Manager
|
||||
|
||||
// true if the node is being run with staking enabled
|
||||
StakingEnabled bool
|
||||
stakingEnabled bool
|
||||
|
||||
// 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
|
||||
|
@ -159,6 +183,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
|
||||
|
@ -186,6 +211,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()
|
||||
|
||||
|
@ -327,12 +358,12 @@ func (vm *VM) initSubnets() error {
|
|||
// the Subnet that validates the chain
|
||||
func (vm *VM) createChain(tx *CreateChainTx) {
|
||||
// The validators that compose the Subnet that validates this chain
|
||||
validators, subnetExists := vm.Validators.GetValidatorSet(tx.SubnetID)
|
||||
validators, subnetExists := vm.validators.GetValidatorSet(tx.SubnetID)
|
||||
if !subnetExists {
|
||||
vm.Ctx.Log.Error("blockchain %s validated by Subnet %s but couldn't get that Subnet. Blockchain not created")
|
||||
return
|
||||
}
|
||||
if !validators.Contains(vm.Ctx.NodeID) && vm.StakingEnabled { // This node doesn't validate this blockchain
|
||||
if !validators.Contains(vm.Ctx.NodeID) && vm.stakingEnabled { // This node doesn't validate this blockchain
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -345,7 +376,7 @@ func (vm *VM) createChain(tx *CreateChainTx) {
|
|||
for _, fxID := range tx.FxIDs {
|
||||
chainParams.FxAliases = append(chainParams.FxAliases, fxID.String())
|
||||
}
|
||||
vm.ChainManager.CreateChain(chainParams)
|
||||
vm.chainManager.CreateChain(chainParams)
|
||||
}
|
||||
|
||||
// Shutdown this blockchain
|
||||
|
@ -383,6 +414,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)
|
||||
|
@ -547,9 +596,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
|
||||
}
|
||||
|
@ -723,10 +772,10 @@ func (vm *VM) getValidators(validatorEvents *EventHeap) []validators.Validator {
|
|||
|
||||
// update the node's validator manager to contain the current validator set of the given Subnet
|
||||
func (vm *VM) updateValidators(subnetID ids.ID) error {
|
||||
validatorSet, subnetInitialized := vm.Validators.GetValidatorSet(subnetID)
|
||||
validatorSet, subnetInitialized := vm.validators.GetValidatorSet(subnetID)
|
||||
if !subnetInitialized { // validator manager doesn't know about this subnet yet
|
||||
validatorSet = validators.NewSet()
|
||||
vm.Validators.PutValidatorSet(subnetID, validatorSet)
|
||||
vm.validators.PutValidatorSet(subnetID, validatorSet)
|
||||
}
|
||||
|
||||
currentValidators, err := vm.getCurrentValidators(vm.DB, subnetID)
|
||||
|
@ -738,3 +787,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
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ava-labs/gecko/chains"
|
||||
|
||||
"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"
|
||||
|
@ -20,7 +20,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,12 +119,12 @@ func defaultVM() *VM {
|
|||
|
||||
vm := &VM{
|
||||
SnowmanVM: &core.SnowmanVM{},
|
||||
ChainManager: chains.MockManager{},
|
||||
chainManager: chains.MockManager{},
|
||||
}
|
||||
|
||||
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()
|
||||
|
@ -1064,5 +1067,163 @@ func TestCreateSubnet(t *testing.T) {
|
|||
if currentValidators.Len() != 0 {
|
||||
t.Fatal("pending validator set should be empty")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// test asset import
|
||||
func TestAtomicImport(t *testing.T) {
|
||||
vm := defaultVM()
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,43 +176,39 @@ 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},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: amount,
|
||||
Locktime: 0,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{destAddr},
|
||||
},
|
||||
outs := []*ava.TransferableOutput{&ava.TransferableOutput{
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: amount,
|
||||
Locktime: 0,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{destAddr},
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
|
||||
if amountSpent > amount {
|
||||
changeAddr, err := w.GetAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outs = append(outs,
|
||||
&avm.TransferableOutput{
|
||||
Asset: avm.Asset{ID: assetID},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: amountSpent - amount,
|
||||
Locktime: 0,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{changeAddr},
|
||||
},
|
||||
outs = append(outs, &ava.TransferableOutput{
|
||||
Asset: ava.Asset{ID: assetID},
|
||||
Out: &secp256k1fx.TransferOutput{
|
||||
Amt: amountSpent - amount,
|
||||
Locktime: 0,
|
||||
OutputOwners: secp256k1fx.OutputOwners{
|
||||
Threshold: 1,
|
||||
Addrs: []ids.ShortID{changeAddr},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
avm.SortTransferableOutputs(outs, w.codec)
|
||||
ava.SortTransferableOutputs(outs, w.codec)
|
||||
|
||||
tx := &avm.Tx{
|
||||
UnsignedTx: &avm.BaseTx{
|
||||
|
|
|
@ -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{
|
||||
|
|
Loading…
Reference in New Issue