From 1a2135db6a7af0179c43b0ba5d675885ee229ae7 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Fri, 13 Mar 2020 19:02:03 -0400 Subject: [PATCH 01/25] Bubble invalid snowman votes --- snow/engine/snowman/engine_test.go | 7 ++- snow/engine/snowman/transitive_test.go | 80 ++++++++++++++++++++++++++ snow/engine/snowman/voter.go | 24 ++++++++ 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/snow/engine/snowman/engine_test.go b/snow/engine/snowman/engine_test.go index e149970..bc4ed59 100644 --- a/snow/engine/snowman/engine_test.go +++ b/snow/engine/snowman/engine_test.go @@ -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 diff --git a/snow/engine/snowman/transitive_test.go b/snow/engine/snowman/transitive_test.go index 1920d8c..a0ebdea 100644 --- a/snow/engine/snowman/transitive_test.go +++ b/snow/engine/snowman/transitive_test.go @@ -280,6 +280,18 @@ func TestEngineQuery(t *testing.T) { if !bytes.Equal(b, blk1.Bytes()) { t.Fatalf("Wrong bytes") } + + vm.GetBlockF = func(blkID ids.ID) (snowman.Block, error) { + switch { + case blkID.Equals(blk.ID()): + return blk, nil + case blkID.Equals(blk1.ID()): + return blk1, nil + } + t.Fatalf("Wrong block requested") + panic("Should have failed") + } + return blk1, nil } te.Put(vdr.ID(), *getRequestID, blk1.ID(), blk1.Bytes()) @@ -418,6 +430,17 @@ func TestEngineMultipleQuery(t *testing.T) { te.Chits(vdr1.ID(), *queryRequestID, blkSet) vm.ParseBlockF = func(b []byte) (snowman.Block, error) { + vm.GetBlockF = func(blkID ids.ID) (snowman.Block, error) { + switch { + case blkID.Equals(blk0.ID()): + return blk0, nil + case blkID.Equals(blk1.ID()): + return blk1, nil + } + t.Fatalf("Wrong block requested") + panic("Should have failed") + } + return blk1, nil } @@ -1076,3 +1099,60 @@ func TestEngineRetryFetch(t *testing.T) { t.Fatalf("Should have requested the block again") } } + +func TestEngineUndeclaredDependencyDeadlock(t *testing.T) { + vdr, _, sender, vm, te, gBlk := setup(t) + + sender.Default(true) + + validBlk := &Blk{ + parent: gBlk, + id: GenerateID(), + height: 1, + status: choices.Processing, + bytes: []byte{1}, + } + + invalidBlk := &Blk{ + parent: validBlk, + id: GenerateID(), + height: 2, + status: choices.Processing, + validity: errors.New("invalid due to an undeclared dependency"), + bytes: []byte{2}, + } + + validBlkID := validBlk.ID() + invalidBlkID := invalidBlk.ID() + + reqID := new(uint32) + sender.PushQueryF = func(_ ids.ShortSet, requestID uint32, _ ids.ID, _ []byte) { + *reqID = requestID + } + + te.insert(validBlk) + + sender.PushQueryF = nil + + te.insert(invalidBlk) + + vm.GetBlockF = func(blkID ids.ID) (snowman.Block, error) { + switch { + case blkID.Equals(validBlkID): + return validBlk, nil + case blkID.Equals(invalidBlkID): + return invalidBlk, nil + } + return nil, errUnknownBlock + } + + votes := ids.Set{} + votes.Add(invalidBlkID) + te.Chits(vdr.ID(), *reqID, votes) + + vm.GetBlockF = nil + + if status := validBlk.Status(); status != choices.Accepted { + t.Fatalf("Should have bubbled invalid votes to the valid parent") + } +} diff --git a/snow/engine/snowman/voter.go b/snow/engine/snowman/voter.go index d9c8a7f..6c515ff 100644 --- a/snow/engine/snowman/voter.go +++ b/snow/engine/snowman/voter.go @@ -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 +} From 1fdb20040af2de33100ae8402aa9d1f7dcd2f0d1 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Sat, 14 Mar 2020 15:38:55 -0400 Subject: [PATCH 02/25] Added test for avalanche bubbling --- snow/consensus/snowstorm/test_tx.go | 3 +- snow/engine/avalanche/transitive_test.go | 94 ++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/snow/consensus/snowstorm/test_tx.go b/snow/consensus/snowstorm/test_tx.go index d5fec1b..12c2f73 100644 --- a/snow/consensus/snowstorm/test_tx.go +++ b/snow/consensus/snowstorm/test_tx.go @@ -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 } diff --git a/snow/engine/avalanche/transitive_test.go b/snow/engine/avalanche/transitive_test.go index 6f5b5ed..69e2d47 100644 --- a/snow/engine/avalanche/transitive_test.go +++ b/snow/engine/avalanche/transitive_test.go @@ -2363,3 +2363,97 @@ 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") + } +} From 799d6dd0e18d56d5dfa10a0ebf842c9b8d77a9e5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 16 Mar 2020 13:36:27 -0400 Subject: [PATCH 03/25] Finished bubble voting --- snow/engine/avalanche/transitive_test.go | 12 ++++++++++ snow/engine/avalanche/voter.go | 28 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/snow/engine/avalanche/transitive_test.go b/snow/engine/avalanche/transitive_test.go index 69e2d47..32799ec 100644 --- a/snow/engine/avalanche/transitive_test.go +++ b/snow/engine/avalanche/transitive_test.go @@ -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()) diff --git a/snow/engine/avalanche/voter.go b/snow/engine/avalanche/voter.go index 72a1b53..0387de3 100644 --- a/snow/engine/avalanche/voter.go +++ b/snow/engine/avalanche/voter.go @@ -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 +} From 47bf310abc899597e91c73b6303522adcc43f923 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 16 Mar 2020 16:38:06 -0400 Subject: [PATCH 04/25] Implemented filtered vertex issuance --- snow/engine/avalanche/issuer.go | 19 ++++-- snow/engine/avalanche/transitive.go | 2 +- snow/engine/avalanche/transitive_test.go | 78 ++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/snow/engine/avalanche/issuer.go b/snow/engine/avalanche/issuer.go index befe973..cdbc969 100644 --- a/snow/engine/avalanche/issuer.go +++ b/snow/engine/avalanche/issuer.go @@ -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) diff --git a/snow/engine/avalanche/transitive.go b/snow/engine/avalanche/transitive.go index 4d6617f..4de2aa5 100644 --- a/snow/engine/avalanche/transitive.go +++ b/snow/engine/avalanche/transitive.go @@ -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) diff --git a/snow/engine/avalanche/transitive_test.go b/snow/engine/avalanche/transitive_test.go index 32799ec..764c75f 100644 --- a/snow/engine/avalanche/transitive_test.go +++ b/snow/engine/avalanche/transitive_test.go @@ -2469,3 +2469,81 @@ func TestEngineUndeclaredDependencyDeadlock(t *testing.T) { 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) +} From 5a8158102f5337f2b2f1a6f67f7b2573aefbd091 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 17 Mar 2020 18:48:40 -0400 Subject: [PATCH 05/25] Added simple readme for xput tests --- xputtest/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 xputtest/README.md diff --git a/xputtest/README.md b/xputtest/README.md new file mode 100644 index 0000000..3b42682 --- /dev/null +++ b/xputtest/README.md @@ -0,0 +1,17 @@ +# Throughput testing + +A throughput test is run in two parts. First a network must be running with at least one of the nodes running a throughput server. To start a throughput server when running a node the `--xput-server-enabled=true` flag should be passed. + +An example single node network can be started with: + +```sh +./build/ava --public-ip=127.0.0.1 --xput-server-port=9652 --xput-server-enabled=true --db-enabled=false --staking-tls-enabled=false --snow-sample-size=1 --snow-quorum-size=1 +``` + +The thoughput node can be started with: + +```sh +./build/xputtest --ip=127.0.0.1 --port=9652 --sp-chain +``` + +The above example with run a throughput test on the simple payment chain. Tests can be run with `--sp-dag` to run throughput tests on the simple payment dag. Tests can be run with `--avm` to run throughput tests on the AVA virtual machine. From c0405e18ff36d9b517a22c98c1fd01f5e90eb951 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 17 Mar 2020 18:49:47 -0400 Subject: [PATCH 06/25] removed accidental commit --- xputtest/README.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 xputtest/README.md diff --git a/xputtest/README.md b/xputtest/README.md deleted file mode 100644 index 3b42682..0000000 --- a/xputtest/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Throughput testing - -A throughput test is run in two parts. First a network must be running with at least one of the nodes running a throughput server. To start a throughput server when running a node the `--xput-server-enabled=true` flag should be passed. - -An example single node network can be started with: - -```sh -./build/ava --public-ip=127.0.0.1 --xput-server-port=9652 --xput-server-enabled=true --db-enabled=false --staking-tls-enabled=false --snow-sample-size=1 --snow-quorum-size=1 -``` - -The thoughput node can be started with: - -```sh -./build/xputtest --ip=127.0.0.1 --port=9652 --sp-chain -``` - -The above example with run a throughput test on the simple payment chain. Tests can be run with `--sp-dag` to run throughput tests on the simple payment dag. Tests can be run with `--avm` to run throughput tests on the AVA virtual machine. From 3200f52659053d0611197ae614c686a2d2bd3ff7 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Wed, 18 Mar 2020 13:57:36 -0400 Subject: [PATCH 07/25] Added Batch Inner calls to be able to atomically commit operations across prefixed DBs --- database/batch.go | 5 +++ database/leveldb/leveldb.go | 3 ++ database/memdb/memdb.go | 3 ++ database/nodb/nodb.go | 3 ++ database/test_database.go | 57 +++++++++++++++++++++++++++++++++ database/versiondb/versiondb.go | 3 ++ 6 files changed, 74 insertions(+) diff --git a/database/batch.go b/database/batch.go index 443fd67..53ce3e5 100644 --- a/database/batch.go +++ b/database/batch.go @@ -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. diff --git a/database/leveldb/leveldb.go b/database/leveldb/leveldb.go index ef5e89c..a763829 100644 --- a/database/leveldb/leveldb.go +++ b/database/leveldb/leveldb.go @@ -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 diff --git a/database/memdb/memdb.go b/database/memdb/memdb.go index 9f6ba58..24b5104 100644 --- a/database/memdb/memdb.go +++ b/database/memdb/memdb.go @@ -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 diff --git a/database/nodb/nodb.go b/database/nodb/nodb.go index 96b5ef0..3f1bceb 100644 --- a/database/nodb/nodb.go +++ b/database/nodb/nodb.go @@ -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 } diff --git a/database/test_database.go b/database/test_database.go index f255d8a..d299bb1 100644 --- a/database/test_database.go +++ b/database/test_database.go @@ -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") diff --git a/database/versiondb/versiondb.go b/database/versiondb/versiondb.go index a475e76..fafa718 100644 --- a/database/versiondb/versiondb.go +++ b/database/versiondb/versiondb.go @@ -289,6 +289,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 { From 186d96ee405ea0d8658a8d714a2153c31ab17fd8 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 19 Mar 2020 14:35:17 -0400 Subject: [PATCH 08/25] Added CommitBatch to the versionDB --- database/versiondb/versiondb.go | 44 +++++++++++++++---- database/versiondb/versiondb_test.go | 66 ++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 9 deletions(-) diff --git a/database/versiondb/versiondb.go b/database/versiondb/versiondb.go index fafa718..6f42131 100644 --- a/database/versiondb/versiondb.go +++ b/database/versiondb/versiondb.go @@ -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 diff --git a/database/versiondb/versiondb_test.go b/database/versiondb/versiondb_test.go index ab3a9bb..70cf8ff 100644 --- a/database/versiondb/versiondb_test.go +++ b/database/versiondb/versiondb_test.go @@ -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() From bb93cc3eeebc5ebd0440b465624aecf84ade428f Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 19 Mar 2020 16:56:58 -0400 Subject: [PATCH 09/25] Added atomic shared memory --- chains/atomic/blockchain_memory.go | 28 +++++++ chains/atomic/blockchain_memory_test.go | 34 ++++++++ chains/atomic/memory.go | 105 ++++++++++++++++++++++++ chains/atomic/memory_test.go | 69 ++++++++++++++++ chains/atomic/writer.go | 21 +++++ chains/atomic/writer_test.go | 61 ++++++++++++++ 6 files changed, 318 insertions(+) create mode 100644 chains/atomic/blockchain_memory.go create mode 100644 chains/atomic/blockchain_memory_test.go create mode 100644 chains/atomic/memory.go create mode 100644 chains/atomic/memory_test.go create mode 100644 chains/atomic/writer.go create mode 100644 chains/atomic/writer_test.go diff --git a/chains/atomic/blockchain_memory.go b/chains/atomic/blockchain_memory.go new file mode 100644 index 0000000..a02a85a --- /dev/null +++ b/chains/atomic/blockchain_memory.go @@ -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) +} diff --git a/chains/atomic/blockchain_memory_test.go b/chains/atomic/blockchain_memory_test.go new file mode 100644 index 0000000..318ae0d --- /dev/null +++ b/chains/atomic/blockchain_memory_test.go @@ -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) +} diff --git a/chains/atomic/memory.go b/chains/atomic/memory.go new file mode 100644 index 0000000..448e6c9 --- /dev/null +++ b/chains/atomic/memory.go @@ -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)) +} diff --git a/chains/atomic/memory_test.go b/chains/atomic/memory_test.go new file mode 100644 index 0000000..f1cf020 --- /dev/null +++ b/chains/atomic/memory_test.go @@ -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) +} diff --git a/chains/atomic/writer.go b/chains/atomic/writer.go new file mode 100644 index 0000000..bacabab --- /dev/null +++ b/chains/atomic/writer.go @@ -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() +} diff --git a/chains/atomic/writer_test.go b/chains/atomic/writer_test.go new file mode 100644 index 0000000..8c79519 --- /dev/null +++ b/chains/atomic/writer_test.go @@ -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}) + } +} From 29282f07acf9c1a369fb7fddee1618037d048a6d Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 19 Mar 2020 17:14:27 -0400 Subject: [PATCH 10/25] Added shared memory to the context --- chains/manager.go | 5 +++++ node/node.go | 17 ++++++++++++++++- snow/context.go | 7 +++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/chains/manager.go b/chains/manager.go index efb4372..952e491 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/gecko/api" "github.com/ava-labs/gecko/api/keystore" + "github.com/ava-labs/gecko/chains/atomic" "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/database/prefixdb" "github.com/ava-labs/gecko/ids" @@ -109,6 +110,7 @@ type manager struct { awaiter Awaiter // Waits for required connections before running bootstrapping server *api.Server // Handles HTTP API calls keystore *keystore.Keystore + sharedMemory *atomic.SharedMemory unblocked bool blockedChains []ChainParameters @@ -135,6 +137,7 @@ func New( awaiter Awaiter, server *api.Server, keystore *keystore.Keystore, + sharedMemory *atomic.SharedMemory, ) Manager { timeoutManager := timeout.Manager{} timeoutManager.Initialize(requestTimeout) @@ -159,6 +162,7 @@ func New( awaiter: awaiter, server: server, keystore: keystore, + sharedMemory: sharedMemory, } m.Initialize() return m @@ -246,6 +250,7 @@ func (m *manager) ForceCreateChain(chain ChainParameters) { NodeID: m.nodeID, HTTP: m.server, Keystore: m.keystore.NewBlockchainKeyStore(chain.ID), + SharedMemory: m.sharedMemory.NewBlockchainSharedMemory(chain.ID), BCLookup: m, } consensusParams := m.consensusParams diff --git a/node/node.go b/node/node.go index 3c53fd5..9fdc92e 100644 --- a/node/node.go +++ b/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 @@ -415,12 +419,20 @@ func (n *Node) initChainManager() { n.ValidatorAPI, &n.APIServer, &n.keystoreServer, + &n.sharedMemory, ) n.chainManager.AddRegistrant(&n.APIServer) } -// initWallet initializes the Wallet service +// initSharedMemory initializes the shared memory for cross chain interation +func (n *Node) initSharedMemory() { + n.Log.Info("initializing SharedMemory") + sharedMemoryDB := prefixdb.New([]byte("shared memory"), n.DB) + n.sharedMemory.Initialize(n.Log, sharedMemoryDB) +} + +// initKeystoreAPI initializes the keystore service // Assumes n.APIServer is already set func (n *Node) initKeystoreAPI() { n.Log.Info("initializing Keystore API") @@ -502,6 +514,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 diff --git a/snow/context.go b/snow/context.go index ce213c1..f359553 100644 --- a/snow/context.go +++ b/snow/context.go @@ -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 } From 638cce84cebc42dbc906fb1c5b7c9a7a0aa2ed03 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 19 Mar 2020 18:36:10 -0400 Subject: [PATCH 11/25] AVM code cleanup --- vms/avm/base_tx.go | 9 +- vms/avm/base_tx_test.go | 238 ++++++++++++++------------------- vms/avm/create_asset_tx.go | 3 +- vms/avm/credential.go | 36 ----- vms/avm/credential_test.go | 36 ----- vms/avm/operables.go | 50 ------- vms/avm/operables_test.go | 68 ---------- vms/avm/operation.go | 7 +- vms/avm/operation_test.go | 32 +---- vms/avm/operation_tx.go | 9 +- vms/avm/prefixed_state_test.go | 12 +- vms/avm/service.go | 28 ++-- vms/avm/state_test.go | 12 +- vms/avm/tx.go | 7 +- vms/avm/tx_test.go | 31 ++--- vms/avm/unique_tx.go | 113 ++++++++-------- vms/avm/vm.go | 12 +- vms/avm/vm_test.go | 49 +++---- xputtest/avmwallet/wallet.go | 2 +- 19 files changed, 250 insertions(+), 504 deletions(-) delete mode 100644 vms/avm/credential.go delete mode 100644 vms/avm/credential_test.go diff --git a/vms/avm/base_tx.go b/vms/avm/base_tx.go index 578aa23..fe646f7 100644 --- a/vms/avm/base_tx.go +++ b/vms/avm/base_tx.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/utils/math" "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/components/verify" ) var ( @@ -155,11 +156,11 @@ func (t *BaseTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error } // SemanticVerify that this transaction is valid to be spent. -func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) error { +func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error { for i, in := range t.Ins { cred := creds[i] - fxIndex, err := vm.getFx(cred.Cred) + fxIndex, err := vm.getFx(cred) if err != nil { return err } @@ -178,7 +179,7 @@ func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) erro return errIncompatibleFx } - err = fx.VerifyTransfer(uTx, utxo.Out, in.In, cred.Cred) + err = fx.VerifyTransfer(uTx, utxo.Out, in.In, cred) if err == nil { continue } @@ -215,7 +216,7 @@ func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) erro return errIncompatibleFx } - if err := fx.VerifyTransfer(uTx, utxo.Out, in.In, cred.Cred); err != nil { + if err := fx.VerifyTransfer(uTx, utxo.Out, in.In, cred); err != nil { return err } } diff --git a/vms/avm/base_tx_test.go b/vms/avm/base_tx_test.go index 1a9b5a6..8c4f02f 100644 --- a/vms/avm/base_tx_test.go +++ b/vms/avm/base_tx_test.go @@ -797,7 +797,7 @@ func TestBaseTxSemanticVerify(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -819,7 +819,7 @@ func TestBaseTxSemanticVerify(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&tx.UnsignedTx) if err != nil { @@ -834,11 +834,9 @@ func TestBaseTxSemanticVerify(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -849,11 +847,11 @@ func TestBaseTxSemanticVerify(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err != nil { @@ -889,7 +887,7 @@ func TestBaseTxSemanticVerifyUnknownFx(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -911,11 +909,9 @@ func TestBaseTxSemanticVerifyUnknownFx(t *testing.T) { }, }, }, - }}} + }} - tx.Creds = append(tx.Creds, &Credential{ - Cred: &testVerifiable{}, - }) + tx.Creds = append(tx.Creds, &testVerifiable{}) b, err := vm.codec.Marshal(tx) if err != nil { @@ -924,11 +920,11 @@ func TestBaseTxSemanticVerifyUnknownFx(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { @@ -964,7 +960,7 @@ func TestBaseTxSemanticVerifyWrongAssetID(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -986,7 +982,7 @@ func TestBaseTxSemanticVerifyWrongAssetID(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&tx.UnsignedTx) if err != nil { @@ -1001,11 +997,9 @@ func TestBaseTxSemanticVerifyWrongAssetID(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1016,11 +1010,11 @@ func TestBaseTxSemanticVerifyWrongAssetID(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { @@ -1068,7 +1062,7 @@ func TestBaseTxSemanticVerifyUnauthorizedFx(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1083,7 +1077,7 @@ func TestBaseTxSemanticVerifyUnauthorizedFx(t *testing.T) { In: &TestTransferable{}, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&tx.UnsignedTx) if err != nil { @@ -1098,11 +1092,9 @@ func TestBaseTxSemanticVerifyUnauthorizedFx(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1113,11 +1105,11 @@ func TestBaseTxSemanticVerifyUnauthorizedFx(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { @@ -1151,7 +1143,7 @@ func TestBaseTxSemanticVerifyInvalidSignature(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1173,13 +1165,11 @@ func TestBaseTxSemanticVerifyInvalidSignature(t *testing.T) { }, }, }, - }}} + }} - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - [crypto.SECP256K1RSigLen]byte{}, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + [crypto.SECP256K1RSigLen]byte{}, }, }) @@ -1190,11 +1180,11 @@ func TestBaseTxSemanticVerifyInvalidSignature(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { @@ -1228,7 +1218,7 @@ func TestBaseTxSemanticVerifyMissingUTXO(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1250,7 +1240,7 @@ func TestBaseTxSemanticVerifyMissingUTXO(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&tx.UnsignedTx) if err != nil { @@ -1265,11 +1255,9 @@ func TestBaseTxSemanticVerifyMissingUTXO(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1280,11 +1268,11 @@ func TestBaseTxSemanticVerifyMissingUTXO(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { @@ -1318,7 +1306,7 @@ func TestBaseTxSemanticVerifyInvalidUTXO(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1340,7 +1328,7 @@ func TestBaseTxSemanticVerifyInvalidUTXO(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&tx.UnsignedTx) if err != nil { @@ -1355,11 +1343,9 @@ func TestBaseTxSemanticVerifyInvalidUTXO(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1370,11 +1356,11 @@ func TestBaseTxSemanticVerifyInvalidUTXO(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { @@ -1407,7 +1393,7 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - pendingTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + pendingTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1444,7 +1430,7 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&pendingTx.UnsignedTx) if err != nil { @@ -1459,11 +1445,9 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - pendingTx.Creds = append(pendingTx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + pendingTx.Creds = append(pendingTx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1486,7 +1470,7 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { vm.PendingTxs() - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1508,7 +1492,7 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err = vm.codec.Marshal(&tx.UnsignedTx) if err != nil { @@ -1522,11 +1506,9 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { fixedSig = [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1537,11 +1519,11 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { @@ -1574,7 +1556,7 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - pendingTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + pendingTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1611,7 +1593,7 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&pendingTx.UnsignedTx) if err != nil { @@ -1626,11 +1608,9 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - pendingTx.Creds = append(pendingTx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + pendingTx.Creds = append(pendingTx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1653,7 +1633,7 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { vm.PendingTxs() - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1675,7 +1655,7 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err = vm.codec.Marshal(&tx.UnsignedTx) if err != nil { @@ -1689,11 +1669,9 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { fixedSig = [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1704,11 +1682,11 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { @@ -1755,7 +1733,7 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - pendingTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + pendingTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1792,7 +1770,7 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&pendingTx.UnsignedTx) if err != nil { @@ -1807,11 +1785,9 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - pendingTx.Creds = append(pendingTx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + pendingTx.Creds = append(pendingTx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1834,7 +1810,7 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { vm.PendingTxs() - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1856,11 +1832,9 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { }, }, }, - }}} + }} - tx.Creds = append(tx.Creds, &Credential{ - Cred: &testVerifiable{}, - }) + tx.Creds = append(tx.Creds, &testVerifiable{}) b, err = vm.codec.Marshal(tx) if err != nil { @@ -1869,11 +1843,11 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { @@ -1920,7 +1894,7 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - pendingTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + pendingTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -1957,7 +1931,7 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&pendingTx.UnsignedTx) if err != nil { @@ -1972,11 +1946,9 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - pendingTx.Creds = append(pendingTx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + pendingTx.Creds = append(pendingTx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -1999,7 +1971,7 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { vm.PendingTxs() - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -2021,13 +1993,11 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { }, }, }, - }}} + }} - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - [crypto.SECP256K1RSigLen]byte{}, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + [crypto.SECP256K1RSigLen]byte{}, }, }) @@ -2038,11 +2008,11 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { tx.Initialize(b) uTx := &UniqueTx{ + TxState: &TxState{ + Tx: tx, + }, vm: vm, txID: tx.ID(), - t: &txState{ - tx: tx, - }, } if err := tx.UnsignedTx.SemanticVerify(vm, uTx, tx.Creds); err == nil { diff --git a/vms/avm/create_asset_tx.go b/vms/avm/create_asset_tx.go index c606b6b..086c249 100644 --- a/vms/avm/create_asset_tx.go +++ b/vms/avm/create_asset_tx.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/components/verify" ) const ( @@ -111,7 +112,7 @@ func (t *CreateAssetTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs } // SemanticVerify that this transaction is well-formed. -func (t *CreateAssetTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) error { +func (t *CreateAssetTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error { return t.BaseTx.SemanticVerify(vm, uTx, creds) } diff --git a/vms/avm/credential.go b/vms/avm/credential.go deleted file mode 100644 index d5fb8ee..0000000 --- a/vms/avm/credential.go +++ /dev/null @@ -1,36 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package avm - -import ( - "errors" - - "github.com/ava-labs/gecko/vms/components/verify" -) - -var ( - errNilCredential = errors.New("nil credential is not valid") - errNilFxCredential = errors.New("nil feature extension credential is not valid") -) - -// Credential ... -type Credential struct { - Cred verify.Verifiable `serialize:"true"` -} - -// Credential returns the feature extension credential that this Credential is -// using. -func (cred *Credential) Credential() verify.Verifiable { return cred.Cred } - -// Verify implements the verify.Verifiable interface -func (cred *Credential) Verify() error { - switch { - case cred == nil: - return errNilCredential - case cred.Cred == nil: - return errNilFxCredential - default: - return cred.Cred.Verify() - } -} diff --git a/vms/avm/credential_test.go b/vms/avm/credential_test.go deleted file mode 100644 index 867a89f..0000000 --- a/vms/avm/credential_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package avm - -import ( - "testing" -) - -func TestCredentialVerifyNil(t *testing.T) { - cred := (*Credential)(nil) - if err := cred.Verify(); err == nil { - t.Fatalf("Should have errored due to nil credential") - } -} - -func TestCredentialVerifyNilFx(t *testing.T) { - cred := &Credential{} - if err := cred.Verify(); err == nil { - t.Fatalf("Should have errored due to nil fx credential") - } -} - -func TestCredential(t *testing.T) { - cred := &Credential{ - Cred: &testVerifiable{}, - } - - if err := cred.Verify(); err != nil { - t.Fatal(err) - } - - if cred.Credential() != cred.Cred { - t.Fatalf("Should have returned the fx credential") - } -} diff --git a/vms/avm/operables.go b/vms/avm/operables.go index 7aac3a0..64230cd 100644 --- a/vms/avm/operables.go +++ b/vms/avm/operables.go @@ -9,7 +9,6 @@ import ( "sort" "github.com/ava-labs/gecko/utils" - "github.com/ava-labs/gecko/vms/components/codec" "github.com/ava-labs/gecko/vms/components/verify" ) @@ -21,55 +20,6 @@ var ( errNilOperableFxInput = errors.New("nil operable feature extension input is not valid") ) -// OperableOutput ... -type OperableOutput struct { - Out verify.Verifiable `serialize:"true"` -} - -// Output returns the feature extension output that this Output is using. -func (out *OperableOutput) Output() verify.Verifiable { return out.Out } - -// Verify implements the verify.Verifiable interface -func (out *OperableOutput) Verify() error { - switch { - case out == nil: - return errNilOperableOutput - case out.Out == nil: - return errNilOperableFxOutput - default: - return out.Out.Verify() - } -} - -type innerSortOperableOutputs struct { - outs []*OperableOutput - codec codec.Codec -} - -func (outs *innerSortOperableOutputs) Less(i, j int) bool { - iOut := outs.outs[i] - jOut := outs.outs[j] - - iBytes, err := outs.codec.Marshal(&iOut.Out) - if err != nil { - return false - } - jBytes, err := outs.codec.Marshal(&jOut.Out) - if err != nil { - return false - } - return bytes.Compare(iBytes, jBytes) == -1 -} -func (outs *innerSortOperableOutputs) Len() int { return len(outs.outs) } -func (outs *innerSortOperableOutputs) Swap(i, j int) { o := outs.outs; o[j], o[i] = o[i], o[j] } - -func sortOperableOutputs(outs []*OperableOutput, c codec.Codec) { - sort.Sort(&innerSortOperableOutputs{outs: outs, codec: c}) -} -func isSortedOperableOutputs(outs []*OperableOutput, c codec.Codec) bool { - return sort.IsSorted(&innerSortOperableOutputs{outs: outs, codec: c}) -} - // OperableInput ... type OperableInput struct { UTXOID `serialize:"true"` diff --git a/vms/avm/operables_test.go b/vms/avm/operables_test.go index 98e0996..8f72b7b 100644 --- a/vms/avm/operables_test.go +++ b/vms/avm/operables_test.go @@ -7,76 +7,8 @@ import ( "testing" "github.com/ava-labs/gecko/ids" - "github.com/ava-labs/gecko/vms/components/codec" ) -func TestOperableOutputVerifyNil(t *testing.T) { - oo := (*OperableOutput)(nil) - if err := oo.Verify(); err == nil { - t.Fatalf("Should have errored due to nil operable output") - } -} - -func TestOperableOutputVerifyNilFx(t *testing.T) { - oo := &OperableOutput{} - if err := oo.Verify(); err == nil { - t.Fatalf("Should have errored due to nil operable fx output") - } -} - -func TestOperableOutputVerify(t *testing.T) { - oo := &OperableOutput{ - Out: &testVerifiable{}, - } - if err := oo.Verify(); err != nil { - t.Fatal(err) - } - if oo.Output() != oo.Out { - t.Fatalf("Should have returned the fx output") - } -} - -func TestOperableOutputSorting(t *testing.T) { - c := codec.NewDefault() - c.RegisterType(&TestTransferable{}) - c.RegisterType(&testVerifiable{}) - - outs := []*OperableOutput{ - &OperableOutput{ - Out: &TestTransferable{Val: 1}, - }, - &OperableOutput{ - Out: &TestTransferable{Val: 0}, - }, - &OperableOutput{ - Out: &TestTransferable{Val: 0}, - }, - &OperableOutput{ - Out: &testVerifiable{}, - }, - } - - if isSortedOperableOutputs(outs, c) { - t.Fatalf("Shouldn't be sorted") - } - sortOperableOutputs(outs, c) - if !isSortedOperableOutputs(outs, c) { - t.Fatalf("Should be sorted") - } - if result := outs[0].Out.(*TestTransferable).Val; result != 0 { - t.Fatalf("Val expected: %d ; result: %d", 0, result) - } - if result := outs[1].Out.(*TestTransferable).Val; result != 0 { - t.Fatalf("Val expected: %d ; result: %d", 0, result) - } - if result := outs[2].Out.(*TestTransferable).Val; result != 1 { - t.Fatalf("Val expected: %d ; result: %d", 0, result) - } - if _, ok := outs[3].Out.(*testVerifiable); !ok { - t.Fatalf("testVerifiable expected") - } -} - func TestOperableInputVerifyNil(t *testing.T) { oi := (*OperableInput)(nil) if err := oi.Verify(); err == nil { diff --git a/vms/avm/operation.go b/vms/avm/operation.go index 516e8fa..2fb374f 100644 --- a/vms/avm/operation.go +++ b/vms/avm/operation.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/gecko/utils" "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/components/verify" ) var ( @@ -21,8 +22,8 @@ var ( type Operation struct { Asset `serialize:"true"` - Ins []*OperableInput `serialize:"true"` - Outs []*OperableOutput `serialize:"true"` + Ins []*OperableInput `serialize:"true"` + Outs []verify.Verifiable `serialize:"true"` } // Verify implements the verify.Verifiable interface @@ -48,7 +49,7 @@ func (op *Operation) Verify(c codec.Codec) error { return err } } - if !isSortedOperableOutputs(op.Outs, c) { + if !isSortedVerifiables(op.Outs, c) { return errOutputsNotSorted } diff --git a/vms/avm/operation_test.go b/vms/avm/operation_test.go index 9215448..16be6e0 100644 --- a/vms/avm/operation_test.go +++ b/vms/avm/operation_test.go @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/components/verify" ) func TestOperationVerifyNil(t *testing.T) { @@ -45,21 +46,6 @@ func TestOperationVerifyInvalidInput(t *testing.T) { } } -func TestOperationVerifyInvalidOutput(t *testing.T) { - c := codec.NewDefault() - op := &Operation{ - Asset: Asset{ - ID: ids.Empty, - }, - Outs: []*OperableOutput{ - &OperableOutput{}, - }, - } - if err := op.Verify(c); err == nil { - t.Fatalf("Should have errored due to an invalid output") - } -} - func TestOperationVerifyInputsNotSorted(t *testing.T) { c := codec.NewDefault() op := &Operation{ @@ -96,13 +82,9 @@ func TestOperationVerifyOutputsNotSorted(t *testing.T) { Asset: Asset{ ID: ids.Empty, }, - Outs: []*OperableOutput{ - &OperableOutput{ - Out: &TestTransferable{Val: 1}, - }, - &OperableOutput{ - Out: &TestTransferable{Val: 0}, - }, + Outs: []verify.Verifiable{ + &TestTransferable{Val: 1}, + &TestTransferable{Val: 0}, }, } if err := op.Verify(c); err == nil { @@ -116,10 +98,8 @@ func TestOperationVerify(t *testing.T) { Asset: Asset{ ID: ids.Empty, }, - Outs: []*OperableOutput{ - &OperableOutput{ - Out: &testVerifiable{}, - }, + Outs: []verify.Verifiable{ + &testVerifiable{}, }, } if err := op.Verify(c); err != nil { diff --git a/vms/avm/operation_tx.go b/vms/avm/operation_tx.go index 07d8947..a9d49b9 100644 --- a/vms/avm/operation_tx.go +++ b/vms/avm/operation_tx.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/components/verify" ) var ( @@ -63,7 +64,7 @@ func (t *OperationTx) UTXOs() []*UTXO { Asset: Asset{ ID: asset, }, - Out: out.Out, + Out: out, }) } } @@ -106,7 +107,7 @@ func (t *OperationTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs i } // SemanticVerify that this transaction is well-formed. -func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) error { +func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error { if err := t.BaseTx.SemanticVerify(vm, uTx, creds); err != nil { return err } @@ -123,7 +124,7 @@ func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) ins = append(ins, in.In) cred := creds[i+offset] - credIntfs = append(credIntfs, cred.Cred) + credIntfs = append(credIntfs, cred) utxoID := in.InputID() utxo, err := vm.state.UTXO(utxoID) @@ -165,7 +166,7 @@ func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) } offset += len(op.Ins) for _, out := range op.Outs { - outs = append(outs, out.Out) + outs = append(outs, out) } var fxObj interface{} diff --git a/vms/avm/prefixed_state_test.go b/vms/avm/prefixed_state_test.go index 2b5d739..d28e568 100644 --- a/vms/avm/prefixed_state_test.go +++ b/vms/avm/prefixed_state_test.go @@ -29,7 +29,7 @@ func TestPrefixedSetsAndGets(t *testing.T) { Out: &testVerifiable{}, } - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -51,7 +51,7 @@ func TestPrefixedSetsAndGets(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(tx.UnsignedTx) if err != nil { @@ -66,11 +66,9 @@ func TestPrefixedSetsAndGets(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) diff --git a/vms/avm/service.go b/vms/avm/service.go index 94121ad..778eedf 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -163,7 +163,7 @@ func (service *Service) GetAssetDescription(_ *http.Request, args *GetAssetDescr if status := tx.Status(); !status.Fetched() { return errUnknownAssetID } - createAssetTx, ok := tx.t.tx.UnsignedTx.(*CreateAssetTx) + createAssetTx, ok := tx.UnsignedTx.(*CreateAssetTx) if !ok { return errTxNotCreateAsset } @@ -700,7 +700,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) cred.Sigs = append(cred.Sigs, fixedSig) } - tx.Creds = append(tx.Creds, &Credential{Cred: cred}) + tx.Creds = append(tx.Creds, cred) } b, err := service.vm.codec.Marshal(tx) @@ -849,19 +849,15 @@ func (service *Service) CreateMintTx(r *http.Request, args *CreateMintTxArgs, re }, }, }, - Outs: []*OperableOutput{ - &OperableOutput{ - &secp256k1fx.MintOutput{ - OutputOwners: out.OutputOwners, - }, + Outs: []verify.Verifiable{ + &secp256k1fx.MintOutput{ + OutputOwners: out.OutputOwners, }, - &OperableOutput{ - &secp256k1fx.TransferOutput{ - Amt: uint64(args.Amount), - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{to}, - }, + &secp256k1fx.TransferOutput{ + Amt: uint64(args.Amount), + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{to}, }, }, }, @@ -963,11 +959,11 @@ func (service *Service) SignMintTx(r *http.Request, args *SignMintTxArgs, reply } if len(tx.Creds) == 0 { - tx.Creds = append(tx.Creds, &Credential{Cred: &secp256k1fx.Credential{}}) + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{}) } cred := tx.Creds[0] - switch cred := cred.Cred.(type) { + switch cred := cred.(type) { case *secp256k1fx.Credential: if len(cred.Sigs) != size { cred.Sigs = make([][crypto.SECP256K1RSigLen]byte, size) diff --git a/vms/avm/state_test.go b/vms/avm/state_test.go index 0485a1e..19ff05c 100644 --- a/vms/avm/state_test.go +++ b/vms/avm/state_test.go @@ -250,7 +250,7 @@ func TestStateTXs(t *testing.T) { t.Fatalf("Should have errored when reading tx") } - tx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -272,7 +272,7 @@ func TestStateTXs(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(tx.UnsignedTx) if err != nil { @@ -287,11 +287,9 @@ func TestStateTXs(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - tx.Creds = append(tx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) diff --git a/vms/avm/tx.go b/vms/avm/tx.go index 4fced32..83f7f8e 100644 --- a/vms/avm/tx.go +++ b/vms/avm/tx.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/components/verify" ) var ( @@ -31,7 +32,7 @@ type UnsignedTx interface { InputUTXOs() []*UTXOID UTXOs() []*UTXO SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error - SemanticVerify(vm *VM, uTx *UniqueTx, creds []*Credential) error + SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error } // Tx is the core operation that can be performed. The tx uses the UTXO model. @@ -42,12 +43,12 @@ type UnsignedTx interface { type Tx struct { UnsignedTx `serialize:"true"` - Creds []*Credential `serialize:"true"` // The credentials of this transaction + Creds []verify.Verifiable `serialize:"true"` // The credentials of this transaction } // Credentials describes the authorization that allows the Inputs to consume the // specified UTXOs. The returned array should not be modified. -func (t *Tx) Credentials() []*Credential { return t.Creds } +func (t *Tx) Credentials() []verify.Verifiable { return t.Creds } // SyntacticVerify verifies that this transaction is well-formed. func (t *Tx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error { diff --git a/vms/avm/tx_test.go b/vms/avm/tx_test.go index bc01f36..e6e8830 100644 --- a/vms/avm/tx_test.go +++ b/vms/avm/tx_test.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/utils/units" "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/components/verify" "github.com/ava-labs/gecko/vms/secp256k1fx" ) @@ -45,7 +46,7 @@ func TestTxInvalidCredential(t *testing.T) { c.RegisterType(&testVerifiable{}) tx := &Tx{ - UnsignedTx: &OperationTx{BaseTx: BaseTx{ + UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -67,11 +68,9 @@ func TestTxInvalidCredential(t *testing.T) { }, }, }, - }}, - Creds: []*Credential{ - &Credential{ - Cred: &testVerifiable{err: errUnneededAddress}, - }, + }, + Creds: []verify.Verifiable{ + &testVerifiable{err: errUnneededAddress}, }, } @@ -99,7 +98,7 @@ func TestTxInvalidUnsignedTx(t *testing.T) { c.RegisterType(&testVerifiable{}) tx := &Tx{ - UnsignedTx: &OperationTx{BaseTx: BaseTx{ + UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -138,14 +137,10 @@ func TestTxInvalidUnsignedTx(t *testing.T) { }, }, }, - }}, - Creds: []*Credential{ - &Credential{ - Cred: &testVerifiable{}, - }, - &Credential{ - Cred: &testVerifiable{}, - }, + }, + Creds: []verify.Verifiable{ + &testVerifiable{}, + &testVerifiable{}, }, } @@ -214,10 +209,8 @@ func TestTxInvalidNumberOfCredentials(t *testing.T) { }, }, }, - Creds: []*Credential{ - &Credential{ - Cred: &testVerifiable{}, - }, + Creds: []verify.Verifiable{ + &testVerifiable{}, }, } diff --git a/vms/avm/unique_tx.go b/vms/avm/unique_tx.go index c7e5115..2fcf563 100644 --- a/vms/avm/unique_tx.go +++ b/vms/avm/unique_tx.go @@ -21,16 +21,19 @@ var ( // UniqueTx provides a de-duplication service for txs. This only provides a // performance boost type UniqueTx struct { + *TxState + vm *VM txID ids.ID - t *txState } -type txState struct { +// TxState ... +type TxState struct { + *Tx + unique, verifiedTx, verifiedState bool validity error - tx *Tx inputs ids.Set inputUTXOs []*UTXOID utxos []*UTXO @@ -42,51 +45,51 @@ type txState struct { } func (tx *UniqueTx) refresh() { - if tx.t == nil { - tx.t = &txState{} + if tx.TxState == nil { + tx.TxState = &TxState{} } - if tx.t.unique { + if tx.unique { return } unique := tx.vm.state.UniqueTx(tx) - prevTx := tx.t.tx + prevTx := tx.Tx if unique == tx { // If no one was in the cache, make sure that there wasn't an // intermediate object whose state I must reflect if status, err := tx.vm.state.Status(tx.ID()); err == nil { - tx.t.status = status - tx.t.unique = true + tx.status = status + tx.unique = true } } else { // If someone is in the cache, they must be up to date // This ensures that every unique tx object points to the same tx state - tx.t = unique.t + tx.TxState = unique.TxState } - if tx.t.tx != nil { + if tx.Tx != nil { return } if prevTx == nil { if innerTx, err := tx.vm.state.Tx(tx.ID()); err == nil { - tx.t.tx = innerTx + tx.Tx = innerTx } } else { - tx.t.tx = prevTx + tx.Tx = prevTx } } // Evict is called when this UniqueTx will no longer be returned from a cache // lookup -func (tx *UniqueTx) Evict() { tx.t.unique = false } // Lock is already held here +func (tx *UniqueTx) Evict() { tx.unique = false } // Lock is already held here func (tx *UniqueTx) setStatus(status choices.Status) error { tx.refresh() - if tx.t.status == status { + if tx.status == status { return nil } - tx.t.status = status + tx.status = status return tx.vm.state.SetStatus(tx.ID(), status) } @@ -125,10 +128,10 @@ func (tx *UniqueTx) Accept() { tx.vm.pubsub.Publish("accepted", txID) - tx.t.deps = nil // Needed to prevent a memory leak + tx.deps = nil // Needed to prevent a memory leak - if tx.t.onDecide != nil { - tx.t.onDecide(choices.Accepted) + if tx.onDecide != nil { + tx.onDecide(choices.Accepted) } } @@ -148,24 +151,24 @@ func (tx *UniqueTx) Reject() { tx.vm.pubsub.Publish("rejected", txID) - tx.t.deps = nil // Needed to prevent a memory leak + tx.deps = nil // Needed to prevent a memory leak - if tx.t.onDecide != nil { - tx.t.onDecide(choices.Rejected) + if tx.onDecide != nil { + tx.onDecide(choices.Rejected) } } // Status returns the current status of this transaction func (tx *UniqueTx) Status() choices.Status { tx.refresh() - return tx.t.status + return tx.status } // Dependencies returns the set of transactions this transaction builds on func (tx *UniqueTx) Dependencies() []snowstorm.Tx { tx.refresh() - if tx.t.tx == nil || len(tx.t.deps) != 0 { - return tx.t.deps + if tx.Tx == nil || len(tx.deps) != 0 { + return tx.deps } txIDs := ids.Set{} @@ -173,61 +176,61 @@ func (tx *UniqueTx) Dependencies() []snowstorm.Tx { txID, _ := in.InputSource() if !txIDs.Contains(txID) { txIDs.Add(txID) - tx.t.deps = append(tx.t.deps, &UniqueTx{ + tx.deps = append(tx.deps, &UniqueTx{ vm: tx.vm, txID: txID, }) } } - for _, assetID := range tx.t.tx.AssetIDs().List() { + for _, assetID := range tx.Tx.AssetIDs().List() { if !txIDs.Contains(assetID) { txIDs.Add(assetID) - tx.t.deps = append(tx.t.deps, &UniqueTx{ + tx.deps = append(tx.deps, &UniqueTx{ vm: tx.vm, txID: assetID, }) } } - return tx.t.deps + return tx.deps } // InputIDs returns the set of utxoIDs this transaction consumes func (tx *UniqueTx) InputIDs() ids.Set { tx.refresh() - if tx.t.tx == nil || tx.t.inputs.Len() != 0 { - return tx.t.inputs + if tx.Tx == nil || tx.inputs.Len() != 0 { + return tx.inputs } for _, utxo := range tx.InputUTXOs() { - tx.t.inputs.Add(utxo.InputID()) + tx.inputs.Add(utxo.InputID()) } - return tx.t.inputs + return tx.inputs } // InputUTXOs returns the utxos that will be consumed on tx acceptance func (tx *UniqueTx) InputUTXOs() []*UTXOID { tx.refresh() - if tx.t.tx == nil || len(tx.t.inputUTXOs) != 0 { - return tx.t.inputUTXOs + if tx.Tx == nil || len(tx.inputUTXOs) != 0 { + return tx.inputUTXOs } - tx.t.inputUTXOs = tx.t.tx.InputUTXOs() - return tx.t.inputUTXOs + tx.inputUTXOs = tx.Tx.InputUTXOs() + return tx.inputUTXOs } // UTXOs returns the utxos that will be added to the UTXO set on tx acceptance func (tx *UniqueTx) UTXOs() []*UTXO { tx.refresh() - if tx.t.tx == nil || len(tx.t.utxos) != 0 { - return tx.t.utxos + if tx.Tx == nil || len(tx.utxos) != 0 { + return tx.utxos } - tx.t.utxos = tx.t.tx.UTXOs() - return tx.t.utxos + tx.utxos = tx.Tx.UTXOs() + return tx.utxos } // Bytes returns the binary representation of this transaction func (tx *UniqueTx) Bytes() []byte { tx.refresh() - return tx.t.tx.Bytes() + return tx.Tx.Bytes() } // Verify the validity of this transaction @@ -248,39 +251,39 @@ func (tx *UniqueTx) Verify() error { func (tx *UniqueTx) SyntacticVerify() error { tx.refresh() - if tx.t.tx == nil { + if tx.Tx == nil { return errUnknownTx } - if tx.t.verifiedTx { - return tx.t.validity + if tx.verifiedTx { + return tx.validity } - tx.t.verifiedTx = true - tx.t.validity = tx.t.tx.SyntacticVerify(tx.vm.ctx, tx.vm.codec, len(tx.vm.fxs)) - return tx.t.validity + tx.verifiedTx = true + tx.validity = tx.Tx.SyntacticVerify(tx.vm.ctx, tx.vm.codec, len(tx.vm.fxs)) + return tx.validity } // SemanticVerify the validity of this transaction func (tx *UniqueTx) SemanticVerify() error { tx.SyntacticVerify() - if tx.t.validity != nil || tx.t.verifiedState { - return tx.t.validity + if tx.validity != nil || tx.verifiedState { + return tx.validity } - tx.t.verifiedState = true - tx.t.validity = tx.t.tx.SemanticVerify(tx.vm, tx) + tx.verifiedState = true + tx.validity = tx.Tx.SemanticVerify(tx.vm, tx) - if tx.t.validity == nil { + if tx.validity == nil { tx.vm.pubsub.Publish("verified", tx.ID()) } - return tx.t.validity + return tx.validity } // UnsignedBytes returns the unsigned bytes of the transaction func (tx *UniqueTx) UnsignedBytes() []byte { - b, err := tx.vm.codec.Marshal(&tx.t.tx.UnsignedTx) + b, err := tx.vm.codec.Marshal(&tx.UnsignedTx) tx.vm.ctx.Log.AssertNoError(err) return b } diff --git a/vms/avm/vm.go b/vms/avm/vm.go index 1b7bd90..2d6a1a5 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -264,7 +264,7 @@ func (vm *VM) IssueTx(b []byte, onDecide func(choices.Status)) (ids.ID, error) { return ids.ID{}, err } vm.issueTx(tx) - tx.t.onDecide = onDecide + tx.onDecide = onDecide return tx.ID(), nil } @@ -402,18 +402,18 @@ func (vm *VM) parseTx(b []byte) (*UniqueTx, error) { rawTx.Initialize(b) tx := &UniqueTx{ + TxState: &TxState{ + Tx: rawTx, + }, vm: vm, txID: rawTx.ID(), - t: &txState{ - tx: rawTx, - }, } if err := tx.SyntacticVerify(); err != nil { return nil, err } if tx.Status() == choices.Unknown { - if err := vm.state.SetTx(tx.ID(), tx.t.tx); err != nil { + if err := vm.state.SetTx(tx.ID(), tx.Tx); err != nil { return nil, err } tx.setStatus(choices.Processing) @@ -449,7 +449,7 @@ func (vm *VM) verifyFxUsage(fxID int, assetID ids.ID) bool { if status := tx.Status(); !status.Fetched() { return false } - createAssetTx, ok := tx.t.tx.UnsignedTx.(*CreateAssetTx) + createAssetTx, ok := tx.UnsignedTx.(*CreateAssetTx) if !ok { return false } diff --git a/vms/avm/vm_test.go b/vms/avm/vm_test.go index 715a8be..58ddcd9 100644 --- a/vms/avm/vm_test.go +++ b/vms/avm/vm_test.go @@ -16,6 +16,7 @@ import ( "github.com/ava-labs/gecko/utils/hashing" "github.com/ava-labs/gecko/utils/units" "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/components/verify" "github.com/ava-labs/gecko/vms/secp256k1fx" ) @@ -300,13 +301,11 @@ func TestTxSerialization(t *testing.T) { Asset: Asset{ ID: asset, }, - Outs: []*OperableOutput{ - &OperableOutput{ - Out: &secp256k1fx.MintOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []verify.Verifiable{ + &secp256k1fx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, }, @@ -441,7 +440,7 @@ 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{ @@ -463,7 +462,7 @@ func TestIssueTx(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&newTx.UnsignedTx) if err != nil { @@ -478,11 +477,9 @@ func TestIssueTx(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - newTx.Creds = append(newTx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + newTx.Creds = append(newTx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -576,7 +573,7 @@ func TestIssueDependentTx(t *testing.T) { key := keys[0] - firstTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + firstTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -608,7 +605,7 @@ func TestIssueDependentTx(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err := vm.codec.Marshal(&firstTx.UnsignedTx) if err != nil { @@ -622,11 +619,9 @@ func TestIssueDependentTx(t *testing.T) { fixedSig := [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - firstTx.Creds = append(firstTx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + firstTx.Creds = append(firstTx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) @@ -641,7 +636,7 @@ func TestIssueDependentTx(t *testing.T) { t.Fatal(err) } - secondTx := &Tx{UnsignedTx: &OperationTx{BaseTx: BaseTx{ + secondTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, Ins: []*TransferableInput{ @@ -661,7 +656,7 @@ func TestIssueDependentTx(t *testing.T) { }, }, }, - }}} + }} unsignedBytes, err = vm.codec.Marshal(&secondTx.UnsignedTx) if err != nil { @@ -675,11 +670,9 @@ func TestIssueDependentTx(t *testing.T) { fixedSig = [crypto.SECP256K1RSigLen]byte{} copy(fixedSig[:], sig) - secondTx.Creds = append(secondTx.Creds, &Credential{ - Cred: &secp256k1fx.Credential{ - Sigs: [][crypto.SECP256K1RSigLen]byte{ - fixedSig, - }, + secondTx.Creds = append(secondTx.Creds, &secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + fixedSig, }, }) diff --git a/xputtest/avmwallet/wallet.go b/xputtest/avmwallet/wallet.go index bffe752..b8f343b 100644 --- a/xputtest/avmwallet/wallet.go +++ b/xputtest/avmwallet/wallet.go @@ -238,7 +238,7 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) ( cred.Sigs = append(cred.Sigs, fixedSig) } - tx.Creds = append(tx.Creds, &avm.Credential{Cred: cred}) + tx.Creds = append(tx.Creds, cred) } b, err := w.codec.Marshal(tx) From aee9d039215b61dcbc580a5d06f8570de269076d Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Fri, 20 Mar 2020 20:56:43 -0400 Subject: [PATCH 12/25] Cleanup + introduction of side effects --- vms/avm/base_tx.go | 89 ++++++-------------------------------- vms/avm/create_asset_tx.go | 6 --- vms/avm/operation_tx.go | 42 ++++-------------- vms/avm/tx.go | 4 +- vms/avm/unique_tx.go | 31 ++++++++++--- vms/avm/utxo_id.go | 8 +++- vms/avm/vm.go | 26 +++++++++++ 7 files changed, 84 insertions(+), 122 deletions(-) diff --git a/vms/avm/base_tx.go b/vms/avm/base_tx.go index fe646f7..89031d6 100644 --- a/vms/avm/base_tx.go +++ b/vms/avm/base_tx.go @@ -6,9 +6,9 @@ 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/codec" "github.com/ava-labs/gecko/vms/components/verify" ) @@ -78,10 +78,8 @@ func (t *BaseTx) UTXOs() []*UTXO { TxID: txID, OutputIndex: uint32(i), }, - Asset: Asset{ - ID: out.AssetID(), - }, - Out: out.Out, + Asset: Asset{ID: out.AssetID()}, + Out: out.Out, } } return utxos @@ -98,10 +96,12 @@ func (t *BaseTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error return errWrongChainID } + fc := 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) { return errOutputsNotSorted @@ -111,45 +111,16 @@ 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) { 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 - } - } - - // TODO: Add the Tx fee to the producedFunds - - for assetID, producedAssetAmount := range producedFunds { - consumedAssetAmount := consumedFunds[assetID] - if producedAssetAmount > consumedAssetAmount { - return errInsufficientFunds - } + if err := fc.Verify(); err != nil { + return err } return t.metadata.Verify() @@ -166,46 +137,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 +158,6 @@ func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable } return nil } + +// SideEffects that this transaction has in addition to the UTXO operations. +func (t *BaseTx) SideEffects(vm *VM) ([]database.Batch, error) { return nil, nil } diff --git a/vms/avm/create_asset_tx.go b/vms/avm/create_asset_tx.go index 086c249..541ac74 100644 --- a/vms/avm/create_asset_tx.go +++ b/vms/avm/create_asset_tx.go @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/vms/components/codec" - "github.com/ava-labs/gecko/vms/components/verify" ) const ( @@ -111,10 +110,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) } diff --git a/vms/avm/operation_tx.go b/vms/avm/operation_tx.go index a9d49b9..90cfcbb 100644 --- a/vms/avm/operation_tx.go +++ b/vms/avm/operation_tx.go @@ -14,6 +14,7 @@ import ( 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") ) @@ -61,10 +62,8 @@ func (t *OperationTx) UTXOs() []*UTXO { TxID: txID, OutputIndex: uint32(len(utxos)), }, - Asset: Asset{ - ID: asset, - }, - Out: out, + Asset: Asset{ID: asset}, + Out: out, }) } } @@ -77,6 +76,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 +127,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 diff --git a/vms/avm/tx.go b/vms/avm/tx.go index 83f7f8e..797558f 100644 --- a/vms/avm/tx.go +++ b/vms/avm/tx.go @@ -6,8 +6,8 @@ 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/codec" "github.com/ava-labs/gecko/vms/components/verify" @@ -31,8 +31,10 @@ type UnsignedTx interface { AssetIDs() ids.Set InputUTXOs() []*UTXOID UTXOs() []*UTXO + SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error + SideEffects(vm *VM) ([]database.Batch, error) } // Tx is the core operation that can be performed. The tx uses the UTXO model. diff --git a/vms/avm/unique_tx.go b/vms/avm/unique_tx.go index 2fcf563..11a73d9 100644 --- a/vms/avm/unique_tx.go +++ b/vms/avm/unique_tx.go @@ -6,6 +6,7 @@ package avm import ( "errors" + "github.com/ava-labs/gecko/chains/atomic" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow/choices" "github.com/ava-labs/gecko/snow/consensus/snowstorm" @@ -98,13 +99,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 @@ -120,12 +128,23 @@ 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) + effects, err := tx.SideEffects(tx.vm) + if err != nil { + tx.vm.ctx.Log.Error("Failed to compute side effects from %s due to %s", txID, err) + return } + 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) + } + + if err := atomic.WriteAll(commitBatch, effects...); err != nil { + tx.vm.ctx.Log.Error("Failed to commit accept %s due to %s", txID, err) + } + + tx.vm.ctx.Log.Verbo("Accepted Tx: %s", txID) + tx.vm.pubsub.Publish("accepted", txID) tx.deps = nil // Needed to prevent a memory leak @@ -137,6 +156,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 diff --git a/vms/avm/utxo_id.go b/vms/avm/utxo_id.go index 9852c5d..55f204c 100644 --- a/vms/avm/utxo_id.go +++ b/vms/avm/utxo_id.go @@ -20,7 +20,9 @@ type UTXOID struct { TxID ids.ID `serialize:"true"` OutputIndex uint32 `serialize:"true"` - // Cached: + // symbolic is false if the UTXO should be part of the DB + symbolic 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.symbolic } + // Verify implements the verify.Verifiable interface func (utxo *UTXOID) Verify() error { switch { diff --git a/vms/avm/vm.go b/vms/avm/vm.go index 2d6a1a5..8a2b428 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -432,6 +432,32 @@ func (vm *VM) issueTx(tx snowstorm.Tx) { } } +func (vm *VM) getUTXO(utxoID *UTXOID) (*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] From f151ab841a69f2c76975d73b3771a034ed757f9e Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Fri, 20 Mar 2020 20:57:12 -0400 Subject: [PATCH 13/25] Added flow checker --- vms/avm/flow_checker.go | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 vms/avm/flow_checker.go diff --git a/vms/avm/flow_checker.go b/vms/avm/flow_checker.go new file mode 100644 index 0000000..02e2821 --- /dev/null +++ b/vms/avm/flow_checker.go @@ -0,0 +1,51 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package avm + +import ( + "github.com/ava-labs/gecko/ids" + "github.com/ava-labs/gecko/utils/math" + "github.com/ava-labs/gecko/utils/wrappers" +) + +// 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 +} From 805b0d55305542438ba20548cfe880bb09998492 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Mon, 23 Mar 2020 11:41:02 -0400 Subject: [PATCH 14/25] wip importTx --- vms/avm/import_tx.go | 195 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 vms/avm/import_tx.go diff --git a/vms/avm/import_tx.go b/vms/avm/import_tx.go new file mode 100644 index 0000000..b8c34fa --- /dev/null +++ b/vms/avm/import_tx.go @@ -0,0 +1,195 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package avm + +import ( + "bytes" + "errors" + "sort" + + "github.com/ava-labs/gecko/database" + "github.com/ava-labs/gecko/ids" + "github.com/ava-labs/gecko/snow" + "github.com/ava-labs/gecko/utils" + "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/components/verify" +) + +// ImportInput ... +type ImportInput struct { + UTXOID ids.ID `serialize:"true"` + Asset `serialize:"true"` + + In FxTransferable `serialize:"true"` +} + +// Input returns the feature extension input that this Input is using. +func (in *ImportInput) Input() FxTransferable { return in.In } + +// Verify implements the verify.Verifiable interface +func (in *ImportInput) Verify() error { + switch { + case in == nil: + return errNilTransferableInput + case in.UTXOID.IsZero(): + return errNilUTXOID + case in.In == nil: + return errNilTransferableFxInput + default: + return verify.All(&in.Asset, in.In) + } +} + +type innerSortImportInputs []*ImportInput + +func (ins innerSortImportInputs) Less(i, j int) bool { + return bytes.Compare(ins[i].AssetID().Bytes(), ins[j].AssetID().Bytes()) == -1 +} +func (ins innerSortImportInputs) Len() int { return len(ins) } +func (ins innerSortImportInputs) Swap(i, j int) { ins[j], ins[i] = ins[i], ins[j] } + +func sortImportInputs(ins []*ImportInput) { sort.Sort(innerSortImportInputs(ins)) } +func isSortedAndUniqueImportInputs(ins []*ImportInput) bool { + return utils.IsSortedAndUnique(innerSortImportInputs(ins)) +} + +// ImportTx is a transaction that imports an asset from another blockchain. +type ImportTx struct { + BaseTx `serialize:"true"` + + Outs []*TransferableOutput `serialize:"true"` // The outputs of this transaction + Ins []*ImportInput `serialize:"true"` // The inputs to this transaction +} + +// InputUTXOs track which UTXOs this transaction is consuming. +func (t *ImportTx) InputUTXOs() []*UTXOID { + utxos := t.BaseTx.InputUTXOs() + for _, in := range t.Ins { + utxos = append(utxos, &UTXOID{ + TxID: in.UTXOID, + symbolic: true, + }) + } + 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 +} + +// UTXOs returns the UTXOs transaction is producing. +func (t *ImportTx) UTXOs() []*UTXO { + txID := t.ID() + utxos := t.BaseTx.UTXOs() + + for _, out := range t.Outs { + utxos = append(utxos, &UTXO{ + UTXOID: UTXOID{ + TxID: txID, + OutputIndex: uint32(len(utxos)), + }, + Asset: Asset{ID: out.AssetID()}, + Out: out.Out, + }) + } + + return utxos +} + +var ( + errNoInputs = 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 len(t.Ins) == 0: + return errNoInputs + } + + if err := t.BaseTx.SyntacticVerify(ctx, c, numFxs); err != nil { + return err + } + + fc := 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) { + return errOutputsNotSorted + } + + for _, in := range t.Ins { + if err := in.Verify(); err != nil { + return err + } + fc.Consume(in.AssetID(), in.Input().Amount()) + } + if !isSortedAndUniqueImportInputs(t.Ins) { + return errInputsNotSortedUnique + } + + // TODO: Add the Tx fee to the produced side + + if err := fc.Verify(); err != nil { + return err + } + + return nil +} + +// 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 + } + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := vm.ctx.SharedMemory.GetDatabase(bID) + defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + + 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 + utxo, err := vm.state.UTXO(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 + } + } + return nil +} + +// SideEffects that this transaction has in addition to the UTXO operations. +func (t *ImportTx) SideEffects(vm *VM) ([]database.Batch, error) { return nil, nil } From 01fe74ec6bc6ea3029bb1266affeb0f511d16834 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 23 Mar 2020 16:40:37 -0400 Subject: [PATCH 15/25] wip --- vms/avm/base_tx.go | 25 +-- vms/avm/base_tx_test.go | 183 +++++++----------- vms/avm/create_asset_tx.go | 9 +- vms/avm/create_asset_tx_test.go | 7 +- vms/avm/import_tx.go | 59 ++++-- vms/avm/operables.go | 3 +- vms/avm/operables_test.go | 17 +- vms/avm/operation.go | 3 +- vms/avm/operation_test.go | 43 ++-- vms/avm/operation_tx.go | 11 +- vms/avm/prefixed_state.go | 32 ++- vms/avm/prefixed_state_test.go | 25 ++- vms/avm/service.go | 19 +- vms/avm/state.go | 127 +++--------- vms/avm/state_test.go | 27 ++- vms/avm/transferables.go | 7 +- vms/avm/transferables_test.go | 79 +++----- vms/avm/tx.go | 7 +- vms/avm/tx_test.go | 34 ++-- vms/avm/unique_tx.go | 20 +- vms/avm/vm.go | 16 +- vms/avm/vm_test.go | 25 +-- vms/{avm => components/ava}/asset.go | 2 +- vms/{avm => components/ava}/asset_test.go | 2 +- vms/{avm => components/ava}/flow_checker.go | 8 +- vms/{avm => components/ava}/metadata.go | 15 +- vms/{avm => components/ava}/metadata_test.go | 6 +- vms/components/ava/prefixed_state.go | 67 +++++++ vms/components/ava/state.go | 113 +++++++++++ vms/{avm => components/ava}/utxo.go | 2 +- vms/{avm => components/ava}/utxo_id.go | 8 +- vms/{avm => components/ava}/utxo_id_test.go | 2 +- vms/{avm => components/ava}/utxo_test.go | 7 +- .../add_default_subnet_validator_tx.go | 5 +- xputtest/avmwallet/utxo_set.go | 10 +- xputtest/avmwallet/wallet.go | 9 +- xputtest/avmwallet/wallet_test.go | 19 +- 37 files changed, 552 insertions(+), 501 deletions(-) rename vms/{avm => components/ava}/asset.go (98%) rename vms/{avm => components/ava}/asset_test.go (99%) rename vms/{avm => components/ava}/flow_checker.go (93%) rename vms/{avm => components/ava}/metadata.go (74%) rename vms/{avm => components/ava}/metadata_test.go (88%) create mode 100644 vms/components/ava/prefixed_state.go create mode 100644 vms/components/ava/state.go rename vms/{avm => components/ava}/utxo.go (98%) rename vms/{avm => components/ava}/utxo_id.go (88%) rename vms/{avm => components/ava}/utxo_id_test.go (99%) rename vms/{avm => components/ava}/utxo_test.go (95%) diff --git a/vms/avm/base_tx.go b/vms/avm/base_tx.go index 89031d6..911f0a8 100644 --- a/vms/avm/base_tx.go +++ b/vms/avm/base_tx.go @@ -9,6 +9,7 @@ import ( "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" ) @@ -28,7 +29,7 @@ var ( // BaseTx is the basis of all transactions. type BaseTx struct { - metadata + ava.Metadata NetID uint32 `serialize:"true"` // ID of the network this chain lives on BCID ids.ID `serialize:"true"` // ID of the chain on which this transaction exists (prevents replay attacks) @@ -51,8 +52,8 @@ func (t *BaseTx) Outputs() []*TransferableOutput { return t.Outs } 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,16 +70,16 @@ func (t *BaseTx) AssetIDs() ids.Set { } // UTXOs returns the UTXOs transaction is producing. -func (t *BaseTx) UTXOs() []*UTXO { +func (t *BaseTx) UTXOs() []*ava.UTXO { txID := t.ID() - utxos := make([]*UTXO, len(t.Outs)) + utxos := make([]*ava.UTXO, len(t.Outs)) for i, out := range t.Outs { - utxos[i] = &UTXO{ - UTXOID: UTXOID{ + utxos[i] = &ava.UTXO{ + UTXOID: ava.UTXOID{ TxID: txID, OutputIndex: uint32(i), }, - Asset: Asset{ID: out.AssetID()}, + Asset: ava.Asset{ID: out.AssetID()}, Out: out.Out, } } @@ -96,7 +97,7 @@ func (t *BaseTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error return errWrongChainID } - fc := NewFlowChecker() + fc := ava.NewFlowChecker() for _, out := range t.Outs { if err := out.Verify(); err != nil { return err @@ -123,7 +124,7 @@ func (t *BaseTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error return err } - return t.metadata.Verify() + return t.Metadata.Verify() } // SemanticVerify that this transaction is valid to be spent. @@ -159,5 +160,5 @@ func (t *BaseTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable return nil } -// SideEffects that this transaction has in addition to the UTXO operations. -func (t *BaseTx) SideEffects(vm *VM) ([]database.Batch, error) { return nil, nil } +// ExecuteWithSideEffects writes the batch with any additional side effects +func (t *BaseTx) ExecuteWithSideEffects(_ *VM, batch database.Batch) error { return batch.Write() } diff --git a/vms/avm/base_tx_test.go b/vms/avm/base_tx_test.go index 8c4f02f..4d596e5 100644 --- a/vms/avm/base_tx_test.go +++ b/vms/avm/base_tx_test.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow/engine/common" "github.com/ava-labs/gecko/utils/crypto" + "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/codec" "github.com/ava-labs/gecko/vms/secp256k1fx" ) @@ -79,7 +80,7 @@ func TestBaseTxSerialization(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 12345, OutputOwners: secp256k1fx.OutputOwners{ @@ -91,7 +92,7 @@ func TestBaseTxSerialization(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -100,7 +101,7 @@ func TestBaseTxSerialization(t *testing.T) { }), OutputIndex: 1, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 54321, Input: secp256k1fx.Input{ @@ -139,7 +140,7 @@ func TestBaseTxGetters(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 12345, OutputOwners: secp256k1fx.OutputOwners{ @@ -151,7 +152,7 @@ func TestBaseTxGetters(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -160,7 +161,7 @@ func TestBaseTxGetters(t *testing.T) { }), OutputIndex: 1, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 54321, Input: secp256k1fx.Input{ @@ -219,7 +220,7 @@ func TestBaseTxSyntacticVerify(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 12345, OutputOwners: secp256k1fx.OutputOwners{ @@ -231,7 +232,7 @@ func TestBaseTxSyntacticVerify(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -240,7 +241,7 @@ func TestBaseTxSyntacticVerify(t *testing.T) { }), OutputIndex: 0, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 54321, Input: secp256k1fx.Input{ @@ -290,7 +291,7 @@ func TestBaseTxSyntacticVerifyWrongNetworkID(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 12345, OutputOwners: secp256k1fx.OutputOwners{ @@ -302,7 +303,7 @@ func TestBaseTxSyntacticVerifyWrongNetworkID(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -311,7 +312,7 @@ func TestBaseTxSyntacticVerifyWrongNetworkID(t *testing.T) { }), OutputIndex: 1, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 54321, Input: secp256k1fx.Input{ @@ -344,7 +345,7 @@ func TestBaseTxSyntacticVerifyWrongChainID(t *testing.T) { BCID: ids.Empty, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 12345, OutputOwners: secp256k1fx.OutputOwners{ @@ -356,7 +357,7 @@ func TestBaseTxSyntacticVerifyWrongChainID(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -365,7 +366,7 @@ func TestBaseTxSyntacticVerifyWrongChainID(t *testing.T) { }), OutputIndex: 1, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 54321, Input: secp256k1fx.Input{ @@ -401,7 +402,7 @@ func TestBaseTxSyntacticVerifyInvalidOutput(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -410,7 +411,7 @@ func TestBaseTxSyntacticVerifyInvalidOutput(t *testing.T) { }), OutputIndex: 1, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 54321, Input: secp256k1fx.Input{ @@ -443,7 +444,7 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 2, OutputOwners: secp256k1fx.OutputOwners{ @@ -453,7 +454,7 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) { }, }, &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 1, OutputOwners: secp256k1fx.OutputOwners{ @@ -465,7 +466,7 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -474,7 +475,7 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) { }), OutputIndex: 1, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 54321, Input: secp256k1fx.Input{ @@ -507,7 +508,7 @@ func TestBaseTxSyntacticVerifyInvalidInput(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 12345, OutputOwners: secp256k1fx.OutputOwners{ @@ -544,7 +545,7 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 12345, OutputOwners: secp256k1fx.OutputOwners{ @@ -556,7 +557,7 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -565,7 +566,7 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) { }), OutputIndex: 0, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: math.MaxUint64, Input: secp256k1fx.Input{ @@ -574,7 +575,7 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) { }, }, &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -583,7 +584,7 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) { }), OutputIndex: 1, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 1, Input: secp256k1fx.Input{ @@ -616,7 +617,7 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 2, OutputOwners: secp256k1fx.OutputOwners{ @@ -626,7 +627,7 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) { }, }, &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: math.MaxUint64, OutputOwners: secp256k1fx.OutputOwners{ @@ -638,7 +639,7 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -647,7 +648,7 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) { }), OutputIndex: 0, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 1, Input: secp256k1fx.Input{ @@ -680,7 +681,7 @@ func TestBaseTxSyntacticVerifyInsufficientFunds(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: math.MaxUint64, OutputOwners: secp256k1fx.OutputOwners{ @@ -692,7 +693,7 @@ func TestBaseTxSyntacticVerifyInsufficientFunds(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -701,7 +702,7 @@ func TestBaseTxSyntacticVerifyInsufficientFunds(t *testing.T) { }), OutputIndex: 0, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 1, Input: secp256k1fx.Input{ @@ -734,7 +735,7 @@ func TestBaseTxSyntacticVerifyUninitialized(t *testing.T) { BCID: chainID, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 12345, OutputOwners: secp256k1fx.OutputOwners{ @@ -746,7 +747,7 @@ func TestBaseTxSyntacticVerifyUninitialized(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, @@ -755,7 +756,7 @@ func TestBaseTxSyntacticVerifyUninitialized(t *testing.T) { }), OutputIndex: 0, }, - Asset: Asset{ID: asset}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 54321, Input: secp256k1fx.Input{ @@ -802,13 +803,11 @@ func TestBaseTxSemanticVerify(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -892,13 +891,11 @@ func TestBaseTxSemanticVerifyUnknownFx(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -965,13 +962,11 @@ func TestBaseTxSemanticVerifyWrongAssetID(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: asset, - }, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1067,14 +1062,12 @@ func TestBaseTxSemanticVerifyUnauthorizedFx(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, - In: &TestTransferable{}, + Asset: ava.Asset{ID: genesisTx.ID()}, + In: &TestTransferable{}, }, }, }} @@ -1148,13 +1141,11 @@ func TestBaseTxSemanticVerifyInvalidSignature(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1223,13 +1214,11 @@ func TestBaseTxSemanticVerifyMissingUTXO(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1311,13 +1300,11 @@ func TestBaseTxSemanticVerifyInvalidUTXO(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: math.MaxUint32, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1398,13 +1385,11 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1417,9 +1402,7 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { }, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, Locktime: 0, @@ -1475,13 +1458,11 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: txID, OutputIndex: 2, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1561,13 +1542,11 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1580,9 +1559,7 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { }, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, Locktime: 0, @@ -1638,13 +1615,11 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: txID, OutputIndex: 0, }, - Asset: Asset{ - ID: asset, - }, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1738,13 +1713,11 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1757,9 +1730,7 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { }, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, Locktime: 0, @@ -1815,13 +1786,11 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: txID, OutputIndex: 0, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1899,13 +1868,11 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -1918,9 +1885,7 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { }, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, Locktime: 0, @@ -1976,13 +1941,11 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: txID, OutputIndex: 0, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ diff --git a/vms/avm/create_asset_tx.go b/vms/avm/create_asset_tx.go index 541ac74..da16356 100644 --- a/vms/avm/create_asset_tx.go +++ b/vms/avm/create_asset_tx.go @@ -10,6 +10,7 @@ import ( "unicode" "github.com/ava-labs/gecko/snow" + "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/codec" ) @@ -43,18 +44,18 @@ type CreateAssetTx struct { func (t *CreateAssetTx) InitialStates() []*InitialState { return t.States } // UTXOs returns the UTXOs transaction is producing. -func (t *CreateAssetTx) UTXOs() []*UTXO { +func (t *CreateAssetTx) UTXOs() []*ava.UTXO { txID := t.ID() utxos := t.BaseTx.UTXOs() for _, state := range t.States { for _, out := range state.Outs { - utxos = append(utxos, &UTXO{ - UTXOID: UTXOID{ + utxos = append(utxos, &ava.UTXO{ + UTXOID: ava.UTXOID{ TxID: txID, OutputIndex: uint32(len(utxos)), }, - Asset: Asset{ + Asset: ava.Asset{ ID: txID, }, Out: out, diff --git a/vms/avm/create_asset_tx_test.go b/vms/avm/create_asset_tx_test.go index 2dabd5c..3586b11 100644 --- a/vms/avm/create_asset_tx_test.go +++ b/vms/avm/create_asset_tx_test.go @@ -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" @@ -94,7 +95,7 @@ func TestCreateAssetTxSerialization(t *testing.T) { }), Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ + Asset: ava.Asset{ ID: ids.NewID([32]byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, @@ -125,7 +126,7 @@ func TestCreateAssetTxSerialization(t *testing.T) { }, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xf1, 0xe1, 0xd1, 0xc1, 0xb1, 0xa1, 0x91, 0x81, 0x71, 0x61, 0x51, 0x41, 0x31, 0x21, 0x11, 0x01, @@ -134,7 +135,7 @@ func TestCreateAssetTxSerialization(t *testing.T) { }), OutputIndex: 5, }, - Asset: Asset{ + Asset: ava.Asset{ ID: ids.NewID([32]byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, diff --git a/vms/avm/import_tx.go b/vms/avm/import_tx.go index b8c34fa..6a95c5f 100644 --- a/vms/avm/import_tx.go +++ b/vms/avm/import_tx.go @@ -8,18 +8,26 @@ import ( "errors" "sort" + "github.com/ava-labs/gecko/chains/atomic" + "github.com/ava-labs/gecko/database/versiondb" + "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/utils" + "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/codec" "github.com/ava-labs/gecko/vms/components/verify" ) +var ( + errNilUTXOID = errors.New("nil utxo ID is not valid") +) + // ImportInput ... type ImportInput struct { - UTXOID ids.ID `serialize:"true"` - Asset `serialize:"true"` + UTXOID ids.ID `serialize:"true"` + ava.Asset `serialize:"true"` In FxTransferable `serialize:"true"` } @@ -63,12 +71,12 @@ type ImportTx struct { } // InputUTXOs track which UTXOs this transaction is consuming. -func (t *ImportTx) InputUTXOs() []*UTXOID { +func (t *ImportTx) InputUTXOs() []*ava.UTXOID { utxos := t.BaseTx.InputUTXOs() for _, in := range t.Ins { - utxos = append(utxos, &UTXOID{ - TxID: in.UTXOID, - symbolic: true, + utxos = append(utxos, &ava.UTXOID{ + TxID: in.UTXOID, + Symbol: true, }) } return utxos @@ -84,17 +92,17 @@ func (t *ImportTx) AssetIDs() ids.Set { } // UTXOs returns the UTXOs transaction is producing. -func (t *ImportTx) UTXOs() []*UTXO { +func (t *ImportTx) UTXOs() []*ava.UTXO { txID := t.ID() utxos := t.BaseTx.UTXOs() for _, out := range t.Outs { - utxos = append(utxos, &UTXO{ - UTXOID: UTXOID{ + utxos = append(utxos, &ava.UTXO{ + UTXOID: ava.UTXOID{ TxID: txID, OutputIndex: uint32(len(utxos)), }, - Asset: Asset{ID: out.AssetID()}, + Asset: ava.Asset{ID: out.AssetID()}, Out: out.Out, }) } @@ -119,7 +127,7 @@ func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) return err } - fc := NewFlowChecker() + fc := ava.NewFlowChecker() for _, out := range t.Outs { if err := out.Verify(); err != nil { return err @@ -159,6 +167,8 @@ func (t *ImportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab smDB := vm.ctx.SharedMemory.GetDatabase(bID) defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + state := ava.NewPrefixedState(smDB, vm.codec) + offset := len(t.BaseTx.Ins) for i, in := range t.Ins { cred := creds[i+offset] @@ -170,7 +180,7 @@ func (t *ImportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab fx := vm.fxs[fxIndex].Fx utxoID := in.UTXOID - utxo, err := vm.state.UTXO(utxoID) + utxo, err := state.PlatformUTXO(utxoID) if err != nil { return err } @@ -191,5 +201,26 @@ func (t *ImportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab return nil } -// SideEffects that this transaction has in addition to the UTXO operations. -func (t *ImportTx) SideEffects(vm *VM) ([]database.Batch, error) { return nil, nil } +// ExecuteWithSideEffects writes the batch with any additional side effects +func (t *ImportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := vm.ctx.SharedMemory.GetDatabase(bID) + defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + + vsmDB := versiondb.New(smDB) + + state := ava.NewPrefixedState(vsmDB, vm.codec) + for _, in := range t.Ins { + utxoID := in.UTXOID + if err := state.SetPlatformUTXO(utxoID, nil); err != nil { + return err + } + } + + sharedBatch, err := vsmDB.CommitBatch() + if err != nil { + return err + } + + return atomic.WriteAll(batch, sharedBatch) +} diff --git a/vms/avm/operables.go b/vms/avm/operables.go index 64230cd..588febc 100644 --- a/vms/avm/operables.go +++ b/vms/avm/operables.go @@ -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" ) @@ -22,7 +23,7 @@ var ( // OperableInput ... type OperableInput struct { - UTXOID `serialize:"true"` + ava.UTXOID `serialize:"true"` In verify.Verifiable `serialize:"true"` } diff --git a/vms/avm/operables_test.go b/vms/avm/operables_test.go index 8f72b7b..40d52bf 100644 --- a/vms/avm/operables_test.go +++ b/vms/avm/operables_test.go @@ -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: &testVerifiable{}, } if err := oi.Verify(); err != nil { t.Fatal(err) @@ -41,28 +40,28 @@ 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{}, }, &OperableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{1}), OutputIndex: 1, }, In: &testVerifiable{}, }, &OperableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 0, }, In: &testVerifiable{}, }, &OperableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{1}), OutputIndex: 0, }, @@ -95,7 +94,7 @@ 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, }, diff --git a/vms/avm/operation.go b/vms/avm/operation.go index 2fb374f..773f08b 100644 --- a/vms/avm/operation.go +++ b/vms/avm/operation.go @@ -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"` Outs []verify.Verifiable `serialize:"true"` diff --git a/vms/avm/operation_test.go b/vms/avm/operation_test.go index 16be6e0..3668307 100644 --- a/vms/avm/operation_test.go +++ b/vms/avm/operation_test.go @@ -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,19 +46,17 @@ 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{}, }, &OperableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 0, }, @@ -79,9 +74,7 @@ func TestOperationVerifyOutputsNotSorted(t *testing.T) { c.RegisterType(&TestTransferable{}) op := &Operation{ - Asset: Asset{ - ID: ids.Empty, - }, + Asset: ava.Asset{ID: ids.Empty}, Outs: []verify.Verifiable{ &TestTransferable{Val: 1}, &TestTransferable{Val: 0}, @@ -95,9 +88,7 @@ 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{}, }, @@ -113,12 +104,10 @@ func TestOperationSorting(t *testing.T) { 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, }, @@ -127,12 +116,10 @@ func TestOperationSorting(t *testing.T) { }, }, &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, }, @@ -149,12 +136,10 @@ 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, }, diff --git a/vms/avm/operation_tx.go b/vms/avm/operation_tx.go index 90cfcbb..53983b3 100644 --- a/vms/avm/operation_tx.go +++ b/vms/avm/operation_tx.go @@ -8,6 +8,7 @@ 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" ) @@ -30,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 { @@ -50,19 +51,19 @@ 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}, + Asset: ava.Asset{ID: asset}, Out: out, }) } diff --git a/vms/avm/prefixed_state.go b/vms/avm/prefixed_state.go index 1314857..e6ca8da 100644 --- a/vms/avm/prefixed_state.go +++ b/vms/avm/prefixed_state.go @@ -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. @@ -126,7 +118,7 @@ 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 diff --git a/vms/avm/prefixed_state_test.go b/vms/avm/prefixed_state_test.go index d28e568..e517f96 100644 --- a/vms/avm/prefixed_state_test.go +++ b/vms/avm/prefixed_state_test.go @@ -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" ) @@ -20,12 +21,12 @@ func TestPrefixedSetsAndGets(t *testing.T) { vm.codec.RegisterType(&testVerifiable{}) - 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: &testVerifiable{}, } @@ -34,13 +35,11 @@ func TestPrefixedSetsAndGets(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + 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{ @@ -118,12 +117,12 @@ func TestPrefixedFundingNoAddresses(t *testing.T) { vm.codec.RegisterType(&testVerifiable{}) - 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: &testVerifiable{}, } @@ -141,12 +140,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}, diff --git a/vms/avm/service.go b/vms/avm/service.go index 778eedf..8174afe 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -18,6 +18,7 @@ import ( "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 +76,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) @@ -618,7 +623,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) in := &TransferableInput{ UTXOID: utxo.UTXOID, - Asset: Asset{ID: assetID}, + Asset: ava.Asset{ID: assetID}, In: input, } @@ -638,9 +643,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) outs := []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ - ID: assetID, - }, + Asset: ava.Asset{ID: assetID}, Out: &secp256k1fx.TransferOutput{ Amt: uint64(args.Amount), Locktime: 0, @@ -656,9 +659,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) changeAddr := kc.Keys[0].PublicKey().Address() outs = append(outs, &TransferableOutput{ - Asset: Asset{ - ID: assetID, - }, + Asset: ava.Asset{ID: assetID}, Out: &secp256k1fx.TransferOutput{ Amt: amountSpent - uint64(args.Amount), Locktime: 0, @@ -836,9 +837,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, diff --git a/vms/avm/state.go b/vms/avm/state.go index b9d045f..809867a 100644 --- a/vms/avm/state.go +++ b/vms/avm/state.go @@ -8,169 +8,98 @@ 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) + s.Cache.Put(id, tx) + return s.DB.Put(id.Bytes(), tx.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 idsIntf, found := s.Cache.Get(id); found { if idSlice, ok := idsIntf.([]ids.ID); ok { return idSlice, 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 } idSlice := []ids.ID(nil) - if err := s.vm.codec.Unmarshal(bytes, &idSlice); err != nil { + if err := s.Codec.Unmarshal(bytes, &idSlice); err != nil { return nil, err } - s.c.Put(id, idSlice) + 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.c.Evict(id) - return s.vm.db.Delete(id.Bytes()) + s.Cache.Evict(id) + return s.DB.Delete(id.Bytes()) } - s.c.Put(id, idSlice) + s.Cache.Put(id, idSlice) - bytes, err := s.vm.codec.Marshal(idSlice) + bytes, err := s.Codec.Marshal(idSlice) if err != nil { return err } - return s.vm.db.Put(id.Bytes(), bytes) + return s.DB.Put(id.Bytes(), bytes) } diff --git a/vms/avm/state_test.go b/vms/avm/state_test.go index 19ff05c..d0d860e 100644 --- a/vms/avm/state_test.go +++ b/vms/avm/state_test.go @@ -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 { @@ -180,12 +181,12 @@ func TestStateUTXOs(t *testing.T) { 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}, + Asset: ava.Asset{ID: ids.Empty}, Out: &testVerifiable{}, } @@ -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") @@ -255,13 +256,11 @@ func TestStateTXs(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + 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{ @@ -312,7 +311,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 +338,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") diff --git a/vms/avm/transferables.go b/vms/avm/transferables.go index e5f3879..154396f 100644 --- a/vms/avm/transferables.go +++ b/vms/avm/transferables.go @@ -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" ) @@ -23,7 +24,7 @@ var ( // TransferableOutput ... type TransferableOutput struct { - Asset `serialize:"true"` + ava.Asset `serialize:"true"` Out FxTransferable `serialize:"true"` } @@ -87,8 +88,8 @@ func IsSortedTransferableOutputs(outs []*TransferableOutput, c codec.Codec) bool // TransferableInput ... type TransferableInput struct { - UTXOID `serialize:"true"` - Asset `serialize:"true"` + ava.UTXOID `serialize:"true"` + ava.Asset `serialize:"true"` In FxTransferable `serialize:"true"` } diff --git a/vms/avm/transferables_test.go b/vms/avm/transferables_test.go index 24accc9..82bf7b4 100644 --- a/vms/avm/transferables_test.go +++ b/vms/avm/transferables_test.go @@ -9,6 +9,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/secp256k1fx" ) @@ -21,11 +22,7 @@ func TestTransferableOutputVerifyNil(t *testing.T) { } func TestTransferableOutputVerifyNilFx(t *testing.T) { - to := &TransferableOutput{ - Asset: Asset{ - ID: ids.Empty, - }, - } + to := &TransferableOutput{Asset: ava.Asset{ID: ids.Empty}} if err := to.Verify(); err == nil { t.Fatalf("Should have errored due to nil transferable fx output") } @@ -33,12 +30,8 @@ func TestTransferableOutputVerifyNilFx(t *testing.T) { func TestTransferableOutputVerify(t *testing.T) { to := &TransferableOutput{ - Asset: Asset{ - ID: ids.Empty, - }, - Out: &TestTransferable{ - Val: 1, - }, + Asset: ava.Asset{ID: ids.Empty}, + Out: &TestTransferable{Val: 1}, } if err := to.Verify(); err != nil { t.Fatal(err) @@ -54,34 +47,24 @@ func TestTransferableOutputSorting(t *testing.T) { outs := []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ - ID: ids.NewID([32]byte{1}), - }, - Out: &TestTransferable{Val: 1}, + Asset: ava.Asset{ID: ids.NewID([32]byte{1})}, + Out: &TestTransferable{Val: 1}, }, &TransferableOutput{ - Asset: Asset{ - ID: ids.Empty, - }, - Out: &TestTransferable{Val: 1}, + Asset: ava.Asset{ID: ids.Empty}, + Out: &TestTransferable{Val: 1}, }, &TransferableOutput{ - Asset: Asset{ - ID: ids.NewID([32]byte{1}), - }, - Out: &TestTransferable{Val: 0}, + Asset: ava.Asset{ID: ids.NewID([32]byte{1})}, + Out: &TestTransferable{Val: 0}, }, &TransferableOutput{ - Asset: Asset{ - ID: ids.Empty, - }, - Out: &TestTransferable{Val: 0}, + Asset: ava.Asset{ID: ids.Empty}, + Out: &TestTransferable{Val: 0}, }, &TransferableOutput{ - Asset: Asset{ - ID: ids.Empty, - }, - Out: &TestTransferable{Val: 0}, + Asset: ava.Asset{ID: ids.Empty}, + Out: &TestTransferable{Val: 0}, }, } @@ -132,7 +115,7 @@ func TestTransferableOutputSerialization(t *testing.T) { } out := &TransferableOutput{ - Asset: Asset{ + Asset: ava.Asset{ ID: ids.NewID([32]byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, @@ -182,8 +165,8 @@ func TestTransferableInputVerifyNil(t *testing.T) { func TestTransferableInputVerifyNilFx(t *testing.T) { ti := &TransferableInput{ - UTXOID: UTXOID{TxID: ids.Empty}, - Asset: Asset{ID: ids.Empty}, + UTXOID: ava.UTXOID{TxID: ids.Empty}, + Asset: ava.Asset{ID: ids.Empty}, } if err := ti.Verify(); err == nil { t.Fatalf("Should have errored due to nil transferable fx input") @@ -192,8 +175,8 @@ func TestTransferableInputVerifyNilFx(t *testing.T) { func TestTransferableInputVerify(t *testing.T) { ti := &TransferableInput{ - UTXOID: UTXOID{TxID: ids.Empty}, - Asset: Asset{ID: ids.Empty}, + UTXOID: ava.UTXOID{TxID: ids.Empty}, + Asset: ava.Asset{ID: ids.Empty}, In: &TestTransferable{}, } if err := ti.Verify(); err != nil { @@ -210,35 +193,35 @@ func TestTransferableInputSorting(t *testing.T) { ins := []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{1}), OutputIndex: 1, }, - Asset: Asset{ID: ids.Empty}, + Asset: ava.Asset{ID: ids.Empty}, In: &TestTransferable{}, }, &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{1}), OutputIndex: 0, }, - Asset: Asset{ID: ids.Empty}, + Asset: ava.Asset{ID: ids.Empty}, In: &TestTransferable{}, }, &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 1, }, - Asset: Asset{ID: ids.Empty}, + Asset: ava.Asset{ID: ids.Empty}, In: &TestTransferable{}, }, &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 0, }, - Asset: Asset{ID: ids.Empty}, + Asset: ava.Asset{ID: ids.Empty}, In: &TestTransferable{}, }, } @@ -252,11 +235,11 @@ func TestTransferableInputSorting(t *testing.T) { } ins = append(ins, &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 1, }, - Asset: Asset{ID: ids.Empty}, + Asset: ava.Asset{ID: ids.Empty}, In: &TestTransferable{}, }) @@ -289,7 +272,7 @@ func TestTransferableInputSerialization(t *testing.T) { } in := &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xf1, 0xe1, 0xd1, 0xc1, 0xb1, 0xa1, 0x91, 0x81, 0x71, 0x61, 0x51, 0x41, 0x31, 0x21, 0x11, 0x01, @@ -298,7 +281,7 @@ func TestTransferableInputSerialization(t *testing.T) { }), OutputIndex: 5, }, - Asset: Asset{ + Asset: ava.Asset{ ID: ids.NewID([32]byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, diff --git a/vms/avm/tx.go b/vms/avm/tx.go index 797558f..eedcc5a 100644 --- a/vms/avm/tx.go +++ b/vms/avm/tx.go @@ -9,6 +9,7 @@ import ( "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" ) @@ -29,12 +30,12 @@ type UnsignedTx interface { 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 - SideEffects(vm *VM) ([]database.Batch, error) + ExecuteWithSideEffects(vm *VM, batch database.Batch) error } // Tx is the core operation that can be performed. The tx uses the UTXO model. diff --git a/vms/avm/tx_test.go b/vms/avm/tx_test.go index e6e8830..ceece2b 100644 --- a/vms/avm/tx_test.go +++ b/vms/avm/tx_test.go @@ -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" @@ -51,13 +52,11 @@ func TestTxInvalidCredential(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + 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{ @@ -103,13 +102,11 @@ func TestTxInvalidUnsignedTx(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 0, }, - Asset: Asset{ - ID: asset, - }, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 20 * units.KiloAva, Input: secp256k1fx.Input{ @@ -120,13 +117,11 @@ func TestTxInvalidUnsignedTx(t *testing.T) { }, }, &TransferableInput{ - UTXOID: UTXOID{ + 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{ @@ -174,13 +169,8 @@ func TestTxInvalidNumberOfCredentials(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ - TxID: ids.Empty, - OutputIndex: 0, - }, - Asset: Asset{ - ID: asset, - }, + UTXOID: ava.UTXOID{TxID: ids.Empty, OutputIndex: 0}, + Asset: ava.Asset{ID: asset}, In: &secp256k1fx.TransferInput{ Amt: 20 * units.KiloAva, Input: secp256k1fx.Input{ @@ -194,12 +184,10 @@ func TestTxInvalidNumberOfCredentials(t *testing.T) { }, Ops: []*Operation{ &Operation{ - Asset: Asset{ - ID: asset, - }, + Asset: ava.Asset{ID: asset}, Ins: []*OperableInput{ &OperableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 1, }, diff --git a/vms/avm/unique_tx.go b/vms/avm/unique_tx.go index 11a73d9..a69278d 100644 --- a/vms/avm/unique_tx.go +++ b/vms/avm/unique_tx.go @@ -6,10 +6,10 @@ package avm import ( "errors" - "github.com/ava-labs/gecko/chains/atomic" "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 ( @@ -36,8 +36,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 @@ -128,19 +128,15 @@ func (tx *UniqueTx) Accept() { } txID := tx.ID() - effects, err := tx.SideEffects(tx.vm) - if err != nil { - tx.vm.ctx.Log.Error("Failed to compute side effects from %s due to %s", txID, err) - return - } - 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 := atomic.WriteAll(commitBatch, effects...); err != nil { + 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) @@ -229,7 +225,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 @@ -239,7 +235,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 diff --git a/vms/avm/vm.go b/vms/avm/vm.go index 8a2b428..1db2842 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -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" @@ -123,10 +124,11 @@ func (vm *VM) Initialize( } vm.state = &prefixedState{ - state: &state{ - c: &cache.LRU{Size: stateCacheSize}, - vm: vm, - }, + 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}, @@ -270,14 +272,14 @@ func (vm *VM) IssueTx(b []byte, onDecide func(choices.Status)) (ids.ID, error) { // 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,7 +434,7 @@ func (vm *VM) issueTx(tx snowstorm.Tx) { } } -func (vm *VM) getUTXO(utxoID *UTXOID) (*UTXO, error) { +func (vm *VM) getUTXO(utxoID *ava.UTXOID) (*ava.UTXO, error) { inputID := utxoID.InputID() utxo, err := vm.state.UTXO(inputID) if err == nil { diff --git a/vms/avm/vm_test.go b/vms/avm/vm_test.go index 58ddcd9..234fdc7 100644 --- a/vms/avm/vm_test.go +++ b/vms/avm/vm_test.go @@ -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" @@ -298,9 +299,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{ @@ -317,9 +316,7 @@ func TestTxSerialization(t *testing.T) { addr := key.PublicKey().Address() unsignedTx.Outs = append(unsignedTx.Outs, &TransferableOutput{ - Asset: Asset{ - ID: asset, - }, + Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 20 * units.KiloAva, OutputOwners: secp256k1fx.OutputOwners{ @@ -445,13 +442,11 @@ func TestIssueTx(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ - ID: genesisTx.ID(), - }, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -578,11 +573,11 @@ func TestIssueDependentTx(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, }, - Asset: Asset{ID: genesisTx.ID()}, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ @@ -595,7 +590,7 @@ func TestIssueDependentTx(t *testing.T) { }, Outs: []*TransferableOutput{ &TransferableOutput{ - Asset: Asset{ID: genesisTx.ID()}, + Asset: ava.Asset{ID: genesisTx.ID()}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, OutputOwners: secp256k1fx.OutputOwners{ @@ -641,11 +636,11 @@ func TestIssueDependentTx(t *testing.T) { BCID: chainID, Ins: []*TransferableInput{ &TransferableInput{ - UTXOID: UTXOID{ + UTXOID: ava.UTXOID{ TxID: firstTx.ID(), OutputIndex: 0, }, - Asset: Asset{ID: genesisTx.ID()}, + Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ Amt: 50000, Input: secp256k1fx.Input{ diff --git a/vms/avm/asset.go b/vms/components/ava/asset.go similarity index 98% rename from vms/avm/asset.go rename to vms/components/ava/asset.go index fc9ef07..86200be 100644 --- a/vms/avm/asset.go +++ b/vms/components/ava/asset.go @@ -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" diff --git a/vms/avm/asset_test.go b/vms/components/ava/asset_test.go similarity index 99% rename from vms/avm/asset_test.go rename to vms/components/ava/asset_test.go index 209cc81..40d6ea8 100644 --- a/vms/avm/asset_test.go +++ b/vms/components/ava/asset_test.go @@ -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" diff --git a/vms/avm/flow_checker.go b/vms/components/ava/flow_checker.go similarity index 93% rename from vms/avm/flow_checker.go rename to vms/components/ava/flow_checker.go index 02e2821..321f8dd 100644 --- a/vms/avm/flow_checker.go +++ b/vms/components/ava/flow_checker.go @@ -1,14 +1,20 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package avm +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 diff --git a/vms/avm/metadata.go b/vms/components/ava/metadata.go similarity index 74% rename from vms/avm/metadata.go rename to vms/components/ava/metadata.go index fb29b44..3ae9228 100644 --- a/vms/avm/metadata.go +++ b/vms/components/ava/metadata.go @@ -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 diff --git a/vms/avm/metadata_test.go b/vms/components/ava/metadata_test.go similarity index 88% rename from vms/avm/metadata_test.go rename to vms/components/ava/metadata_test.go index 09c559b..bd6563b 100644 --- a/vms/avm/metadata_test.go +++ b/vms/components/ava/metadata_test.go @@ -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") } diff --git a/vms/components/ava/prefixed_state.go b/vms/components/ava/prefixed_state.go new file mode 100644 index 0000000..f305392 --- /dev/null +++ b/vms/components/ava/prefixed_state.go @@ -0,0 +1,67 @@ +// (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/vms/components/codec" +) + +const ( + platformUTXOID uint64 = iota + platformStatusID + avmUTXOID + avmStatusID +) + +const ( + stateCacheSize = 10000 + idCacheSize = 10000 +) + +// 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 { + State + + platformUTXO, platformStatus, avmUTXO, avmStatus cache.Cacher +} + +// NewPrefixedState ... +func NewPrefixedState(db database.Database, codec codec.Codec) *PrefixedState { + return &PrefixedState{ + State: State{ + Cache: &cache.LRU{Size: stateCacheSize}, + DB: db, + Codec: codec, + }, + platformUTXO: &cache.LRU{Size: idCacheSize}, + platformStatus: &cache.LRU{Size: idCacheSize}, + avmUTXO: &cache.LRU{Size: idCacheSize}, + avmStatus: &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.UTXO(UniqueID(id, platformUTXOID, s.platformUTXO)) +} + +// SetPlatformUTXO saves the provided utxo to platform's storage. +func (s *PrefixedState) SetPlatformUTXO(id ids.ID, utxo *UTXO) error { + return s.SetUTXO(UniqueID(id, platformUTXOID, s.platformUTXO), utxo) +} + +// PlatformStatus returns the platform status from storage. +func (s *PrefixedState) PlatformStatus(id ids.ID) (choices.Status, error) { + return s.Status(UniqueID(id, platformStatusID, s.platformStatus)) +} + +// SetPlatformStatus saves the provided platform status to storage. +func (s *PrefixedState) SetPlatformStatus(id ids.ID, status choices.Status) error { + return s.SetStatus(UniqueID(id, platformStatusID, s.platformStatus), status) +} diff --git a/vms/components/ava/state.go b/vms/components/ava/state.go new file mode 100644 index 0000000..f170d50 --- /dev/null +++ b/vms/components/ava/state.go @@ -0,0 +1,113 @@ +// (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) +} diff --git a/vms/avm/utxo.go b/vms/components/ava/utxo.go similarity index 98% rename from vms/avm/utxo.go rename to vms/components/ava/utxo.go index e431e55..0552334 100644 --- a/vms/avm/utxo.go +++ b/vms/components/ava/utxo.go @@ -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" diff --git a/vms/avm/utxo_id.go b/vms/components/ava/utxo_id.go similarity index 88% rename from vms/avm/utxo_id.go rename to vms/components/ava/utxo_id.go index 55f204c..b692325 100644 --- a/vms/avm/utxo_id.go +++ b/vms/components/ava/utxo_id.go @@ -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,8 +20,8 @@ type UTXOID struct { TxID ids.ID `serialize:"true"` OutputIndex uint32 `serialize:"true"` - // symbolic is false if the UTXO should be part of the DB - symbolic bool + // 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 } @@ -39,7 +39,7 @@ func (utxo *UTXOID) InputID() ids.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.symbolic } +func (utxo *UTXOID) Symbolic() bool { return utxo.Symbol } // Verify implements the verify.Verifiable interface func (utxo *UTXOID) Verify() error { diff --git a/vms/avm/utxo_id_test.go b/vms/components/ava/utxo_id_test.go similarity index 99% rename from vms/avm/utxo_id_test.go rename to vms/components/ava/utxo_id_test.go index fed513f..7944961 100644 --- a/vms/avm/utxo_id_test.go +++ b/vms/components/ava/utxo_id_test.go @@ -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" diff --git a/vms/avm/utxo_test.go b/vms/components/ava/utxo_test.go similarity index 95% rename from vms/avm/utxo_test.go rename to vms/components/ava/utxo_test.go index 6f043db..07b067a 100644 --- a/vms/avm/utxo_test.go +++ b/vms/components/ava/utxo_test.go @@ -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, diff --git a/vms/platformvm/add_default_subnet_validator_tx.go b/vms/platformvm/add_default_subnet_validator_tx.go index bf398c7..10d41fe 100644 --- a/vms/platformvm/add_default_subnet_validator_tx.go +++ b/vms/platformvm/add_default_subnet_validator_tx.go @@ -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 diff --git a/xputtest/avmwallet/utxo_set.go b/xputtest/avmwallet/utxo_set.go index 8f2cb69..c346969 100644 --- a/xputtest/avmwallet/utxo_set.go +++ b/xputtest/avmwallet/utxo_set.go @@ -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 diff --git a/xputtest/avmwallet/wallet.go b/xputtest/avmwallet/wallet.go index b8f343b..e93b1ef 100644 --- a/xputtest/avmwallet/wallet.go +++ b/xputtest/avmwallet/wallet.go @@ -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" ) @@ -92,7 +93,7 @@ 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) { +func (w *Wallet) AddUTXO(utxo *ava.UTXO) { out, ok := utxo.Out.(avm.FxTransferable) if !ok { return @@ -157,7 +158,7 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) ( in := &avm.TransferableInput{ UTXOID: utxo.UTXOID, - Asset: avm.Asset{ID: assetID}, + Asset: ava.Asset{ID: assetID}, In: input, } @@ -177,7 +178,7 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) ( outs := []*avm.TransferableOutput{ &avm.TransferableOutput{ - Asset: avm.Asset{ID: assetID}, + Asset: ava.Asset{ID: assetID}, Out: &secp256k1fx.TransferOutput{ Amt: amount, Locktime: 0, @@ -196,7 +197,7 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) ( } outs = append(outs, &avm.TransferableOutput{ - Asset: avm.Asset{ID: assetID}, + Asset: ava.Asset{ID: assetID}, Out: &secp256k1fx.TransferOutput{ Amt: amountSpent - amount, Locktime: 0, diff --git a/xputtest/avmwallet/wallet_test.go b/xputtest/avmwallet/wallet_test.go index 377368d..f6b534f 100644 --- a/xputtest/avmwallet/wallet_test.go +++ b/xputtest/avmwallet/wallet_test.go @@ -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{ From 552e63f2ebfb250c54ec37e0bed84542516d656c Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Tue, 24 Mar 2020 12:39:25 -0400 Subject: [PATCH 16/25] Added export Tx to the AVM --- vms/avm/export_tx.go | 164 +++++++++++ vms/avm/export_tx_test.go | 390 +++++++++++++++++++++++++++ vms/avm/import_tx.go | 75 ++---- vms/avm/import_tx_test.go | 367 +++++++++++++++++++++++++ vms/avm/service_test.go | 4 +- vms/avm/static_service.go | 2 + vms/avm/static_service_test.go | 91 ++++++- vms/avm/unique_tx.go | 13 +- vms/avm/vm.go | 32 +-- vms/avm/vm_test.go | 12 +- vms/components/ava/prefixed_state.go | 20 ++ xputtest/avmwallet/wallet.go | 2 + 12 files changed, 1075 insertions(+), 97 deletions(-) create mode 100644 vms/avm/export_tx.go create mode 100644 vms/avm/export_tx_test.go create mode 100644 vms/avm/import_tx_test.go diff --git a/vms/avm/export_tx.go b/vms/avm/export_tx.go new file mode 100644 index 0000000..d44741c --- /dev/null +++ b/vms/avm/export_tx.go @@ -0,0 +1,164 @@ +// (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/snow/choices" + "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 a transaction that exports an asset to another blockchain. +type ExportTx struct { + BaseTx `serialize:"true"` + + Outs []*TransferableOutput `serialize:"true"` // The outputs of this transaction + Ins []*TransferableInput `serialize:"true"` // The inputs to this transaction +} + +// InputUTXOs track which UTXOs this transaction is consuming. +func (t *ExportTx) InputUTXOs() []*ava.UTXOID { + utxos := t.BaseTx.InputUTXOs() + for _, in := range t.Ins { + utxos = append(utxos, &in.UTXOID) + } + return utxos +} + +// AssetIDs returns the IDs of the assets this transaction depends on +func (t *ExportTx) AssetIDs() ids.Set { + assets := t.BaseTx.AssetIDs() + for _, in := range t.Ins { + assets.Add(in.AssetID()) + } + return assets +} + +var ( + errNoExportInputs = errors.New("no export inputs") +) + +// SyntacticVerify that this transaction is well-formed. +func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error { + switch { + case t == nil: + return errNilTx + case len(t.Ins) == 0: + return errNoExportInputs + } + + if err := t.BaseTx.SyntacticVerify(ctx, c, numFxs); err != nil { + return err + } + + 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) { + return errOutputsNotSorted + } + + for _, in := range t.Ins { + if err := in.Verify(); err != nil { + return err + } + fc.Consume(in.AssetID(), in.Input().Amount()) + } + if !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 *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error { + if err := t.BaseTx.SemanticVerify(vm, uTx, creds); err != nil { + return err + } + + 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 + + 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 + } + } + return nil +} + +// ExecuteWithSideEffects writes the batch with any additional side effects +func (t *ExportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { + txID := t.ID() + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := vm.ctx.SharedMemory.GetDatabase(bID) + defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + + 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, + } + + utxoID := utxo.InputID() + if _, err := state.AVMStatus(utxoID); err == nil { + if err := state.SetAVMStatus(utxoID, choices.Unknown); err != nil { + return err + } + } else if err := state.SetAVMUTXO(utxoID, utxo); err != nil { + return err + } + } + + sharedBatch, err := vsmDB.CommitBatch() + if err != nil { + return err + } + + return atomic.WriteAll(batch, sharedBatch) +} diff --git a/vms/avm/export_tx_test.go b/vms/avm/export_tx_test.go new file mode 100644 index 0000000..8c1a486 --- /dev/null +++ b/vms/avm/export_tx_test.go @@ -0,0 +1,390 @@ +// (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/choices" + "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 base outs: + 0x00, 0x00, 0x00, 0x00, + // number of base inputs: + 0x00, 0x00, 0x00, 0x00, + // 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, + } + + 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: []*TransferableInput{&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) + + ctx.Lock.Lock() + vm := &VM{} + err := vm.Initialize( + ctx, + memdb.New(), + genesisBytes, + issuer, + []*common.Fx{&common.Fx{ + ID: ids.Empty, + Fx: &secp256k1fx.Fx{}, + }}, + ) + if err != nil { + t.Fatal(err) + } + vm.batchTimeout = 0 + + key := keys[0] + + genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) + + tx := &Tx{UnsignedTx: &ExportTx{ + BaseTx: BaseTx{ + NetID: networkID, + BCID: chainID, + }, + Ins: []*TransferableInput{&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: 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(&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() + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := vm.ctx.SharedMemory.GetDatabase(bID) + defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + + 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) + } + if _, err := state.AVMStatus(utxoID); err == nil { + t.Fatalf("should have failed to read the status") + } +} + +// 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) + + ctx.Lock.Lock() + vm := &VM{} + err := vm.Initialize( + ctx, + memdb.New(), + genesisBytes, + issuer, + []*common.Fx{&common.Fx{ + ID: ids.Empty, + Fx: &secp256k1fx.Fx{}, + }}, + ) + if err != nil { + t.Fatal(err) + } + vm.batchTimeout = 0 + + key := keys[0] + + genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) + + tx := &Tx{UnsignedTx: &ExportTx{ + BaseTx: BaseTx{ + NetID: networkID, + BCID: chainID, + }, + Ins: []*TransferableInput{&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: 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(&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) + } + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := vm.ctx.SharedMemory.GetDatabase(bID) + + state := ava.NewPrefixedState(smDB, vm.codec) + + utxo := ava.UTXOID{ + TxID: tx.ID(), + OutputIndex: 0, + } + utxoID := utxo.InputID() + if err := state.SetAVMStatus(utxoID, choices.Accepted); err != nil { + t.Fatal(err) + } + + vm.ctx.SharedMemory.ReleaseDatabase(bID) + + parsedTx.Accept() + + smDB = vm.ctx.SharedMemory.GetDatabase(bID) + defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + + state = ava.NewPrefixedState(smDB, vm.codec) + + if _, err := state.AVMUTXO(utxoID); err == nil { + t.Fatalf("should have failed to read the utxo") + } + if _, err := state.AVMStatus(utxoID); err == nil { + t.Fatalf("should have failed to read the status") + } +} diff --git a/vms/avm/import_tx.go b/vms/avm/import_tx.go index 6a95c5f..d349cf2 100644 --- a/vms/avm/import_tx.go +++ b/vms/avm/import_tx.go @@ -4,17 +4,14 @@ package avm import ( - "bytes" "errors" - "sort" "github.com/ava-labs/gecko/chains/atomic" - "github.com/ava-labs/gecko/database/versiondb" - "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/utils" + "github.com/ava-labs/gecko/snow/choices" "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/codec" "github.com/ava-labs/gecko/vms/components/verify" @@ -24,60 +21,20 @@ var ( errNilUTXOID = errors.New("nil utxo ID is not valid") ) -// ImportInput ... -type ImportInput struct { - UTXOID ids.ID `serialize:"true"` - ava.Asset `serialize:"true"` - - In FxTransferable `serialize:"true"` -} - -// Input returns the feature extension input that this Input is using. -func (in *ImportInput) Input() FxTransferable { return in.In } - -// Verify implements the verify.Verifiable interface -func (in *ImportInput) Verify() error { - switch { - case in == nil: - return errNilTransferableInput - case in.UTXOID.IsZero(): - return errNilUTXOID - case in.In == nil: - return errNilTransferableFxInput - default: - return verify.All(&in.Asset, in.In) - } -} - -type innerSortImportInputs []*ImportInput - -func (ins innerSortImportInputs) Less(i, j int) bool { - return bytes.Compare(ins[i].AssetID().Bytes(), ins[j].AssetID().Bytes()) == -1 -} -func (ins innerSortImportInputs) Len() int { return len(ins) } -func (ins innerSortImportInputs) Swap(i, j int) { ins[j], ins[i] = ins[i], ins[j] } - -func sortImportInputs(ins []*ImportInput) { sort.Sort(innerSortImportInputs(ins)) } -func isSortedAndUniqueImportInputs(ins []*ImportInput) bool { - return utils.IsSortedAndUnique(innerSortImportInputs(ins)) -} - // ImportTx is a transaction that imports an asset from another blockchain. type ImportTx struct { BaseTx `serialize:"true"` Outs []*TransferableOutput `serialize:"true"` // The outputs of this transaction - Ins []*ImportInput `serialize:"true"` // The inputs to this transaction + Ins []*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 { - utxos = append(utxos, &ava.UTXOID{ - TxID: in.UTXOID, - Symbol: true, - }) + in.Symbol = true + utxos = append(utxos, &in.UTXOID) } return utxos } @@ -111,7 +68,7 @@ func (t *ImportTx) UTXOs() []*ava.UTXO { } var ( - errNoInputs = errors.New("no import inputs") + errNoImportInputs = errors.New("no import inputs") ) // SyntacticVerify that this transaction is well-formed. @@ -120,7 +77,7 @@ func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) case t == nil: return errNilTx case len(t.Ins) == 0: - return errNoInputs + return errNoImportInputs } if err := t.BaseTx.SyntacticVerify(ctx, c, numFxs); err != nil { @@ -144,17 +101,13 @@ func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) } fc.Consume(in.AssetID(), in.Input().Amount()) } - if !isSortedAndUniqueImportInputs(t.Ins) { + if !isSortedAndUniqueTransferableInputs(t.Ins) { return errInputsNotSortedUnique } // TODO: Add the Tx fee to the produced side - if err := fc.Verify(); err != nil { - return err - } - - return nil + return fc.Verify() } // SemanticVerify that this transaction is well-formed. @@ -179,7 +132,7 @@ func (t *ImportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab } fx := vm.fxs[fxIndex].Fx - utxoID := in.UTXOID + utxoID := in.UTXOID.InputID() utxo, err := state.PlatformUTXO(utxoID) if err != nil { return err @@ -211,8 +164,12 @@ func (t *ImportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { state := ava.NewPrefixedState(vsmDB, vm.codec) for _, in := range t.Ins { - utxoID := in.UTXOID - if err := state.SetPlatformUTXO(utxoID, nil); err != nil { + utxoID := in.UTXOID.InputID() + if _, err := state.PlatformUTXO(utxoID); err == nil { + if err := state.SetPlatformUTXO(utxoID, nil); err != nil { + return err + } + } else if err := state.SetPlatformStatus(utxoID, choices.Accepted); err != nil { return err } } diff --git a/vms/avm/import_tx_test.go b/vms/avm/import_tx_test.go new file mode 100644 index 0000000..3599850 --- /dev/null +++ b/vms/avm/import_tx_test.go @@ -0,0 +1,367 @@ +// (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/choices" + "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 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, + } + + 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: []*TransferableInput{&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) + + ctx.Lock.Lock() + vm := &VM{} + err := vm.Initialize( + ctx, + memdb.New(), + genesisBytes, + issuer, + []*common.Fx{&common.Fx{ + ID: ids.Empty, + Fx: &secp256k1fx.Fx{}, + }}, + ) + if err != nil { + t.Fatal(err) + } + vm.batchTimeout = 0 + + 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: []*TransferableInput{&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) + + if _, err := vm.IssueTx(tx.Bytes(), nil); err == nil { + t.Fatal(err) + } + + // Provide the platform UTXO: + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := vm.ctx.SharedMemory.GetDatabase(bID) + + utxo := &ava.UTXO{ + UTXOID: utxoID, + Asset: ava.Asset{ID: genesisTx.ID()}, + 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.SetPlatformUTXO(utxoID.InputID(), utxo); err != nil { + t.Fatal(err) + } + + vm.ctx.SharedMemory.ReleaseDatabase(bID) + + 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(bID) + defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + + 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) + + ctx.Lock.Lock() + defer ctx.Lock.Unlock() + + vm := &VM{} + err := vm.Initialize( + ctx, + memdb.New(), + genesisBytes, + issuer, + []*common.Fx{&common.Fx{ + ID: ids.Empty, + Fx: &secp256k1fx.Fx{}, + }}, + ) + if err != nil { + t.Fatal(err) + } + vm.batchTimeout = 0 + + 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: []*TransferableInput{&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() + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := vm.ctx.SharedMemory.GetDatabase(bID) + defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + + 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") + } else if status, err := state.PlatformStatus(utxoSource); err != nil { + t.Fatal(err) + } else if status != choices.Accepted { + t.Fatalf("should have marked the utxo as consumed") + } +} diff --git a/vms/avm/service_test.go b/vms/avm/service_test.go index 16be290..affebfd 100644 --- a/vms/avm/service_test.go +++ b/vms/avm/service_test.go @@ -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) } } diff --git a/vms/avm/static_service.go b/vms/avm/static_service.go index 47ef942..2a463d2 100644 --- a/vms/avm/static_service.go +++ b/vms/avm/static_service.go @@ -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{}) diff --git a/vms/avm/static_service_test.go b/vms/avm/static_service_test.go index 612132e..f9f342f 100644 --- a/vms/avm/static_service_test.go +++ b/vms/avm/static_service_test.go @@ -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}, ) } } diff --git a/vms/avm/unique_tx.go b/vms/avm/unique_tx.go index a69278d..13673fa 100644 --- a/vms/avm/unique_tx.go +++ b/vms/avm/unique_tx.go @@ -289,13 +289,14 @@ 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()) + err := tx.Tx.SemanticVerify(tx.vm, tx) + if 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 diff --git a/vms/avm/vm.go b/vms/avm/vm.go index 1db2842..0d6a460 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -123,25 +123,12 @@ func (vm *VM) Initialize( return errs.Err } - 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}, - } - 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 { @@ -168,6 +155,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 } diff --git a/vms/avm/vm_test.go b/vms/avm/vm_test.go index 234fdc7..1e625e7 100644 --- a/vms/avm/vm_test.go +++ b/vms/avm/vm_test.go @@ -52,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{}) @@ -207,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, @@ -228,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, @@ -249,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, @@ -278,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, @@ -331,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{}) diff --git a/vms/components/ava/prefixed_state.go b/vms/components/ava/prefixed_state.go index f305392..1cd0c8e 100644 --- a/vms/components/ava/prefixed_state.go +++ b/vms/components/ava/prefixed_state.go @@ -65,3 +65,23 @@ func (s *PrefixedState) PlatformStatus(id ids.ID) (choices.Status, error) { func (s *PrefixedState) SetPlatformStatus(id ids.ID, status choices.Status) error { return s.SetStatus(UniqueID(id, platformStatusID, s.platformStatus), status) } + +// AVMUTXO attempts to load a utxo from AVM's storage. +func (s *PrefixedState) AVMUTXO(id ids.ID) (*UTXO, error) { + return s.UTXO(UniqueID(id, avmUTXOID, s.platformUTXO)) +} + +// SetAVMUTXO saves the provided utxo to AVM's storage. +func (s *PrefixedState) SetAVMUTXO(id ids.ID, utxo *UTXO) error { + return s.SetUTXO(UniqueID(id, avmUTXOID, s.platformUTXO), utxo) +} + +// AVMStatus returns the AVM status from storage. +func (s *PrefixedState) AVMStatus(id ids.ID) (choices.Status, error) { + return s.Status(UniqueID(id, avmStatusID, s.platformStatus)) +} + +// SetAVMStatus saves the provided platform status to storage. +func (s *PrefixedState) SetAVMStatus(id ids.ID, status choices.Status) error { + return s.SetStatus(UniqueID(id, avmStatusID, s.platformStatus), status) +} diff --git a/xputtest/avmwallet/wallet.go b/xputtest/avmwallet/wallet.go index e93b1ef..e0cd127 100644 --- a/xputtest/avmwallet/wallet.go +++ b/xputtest/avmwallet/wallet.go @@ -50,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{}), From a6a92510af1b9b2aa201ddd49d4a2b146d8af93f Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Wed, 25 Mar 2020 12:35:13 -0400 Subject: [PATCH 17/25] Added UTXO check in platform VM --- vms/platformvm/common_blocks.go | 12 ++++++++++ vms/platformvm/create_chain_tx.go | 3 +++ vms/platformvm/create_subnet_tx.go | 3 +++ vms/platformvm/standard_block.go | 35 +++++++++++++++++++++++++++++- 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/vms/platformvm/common_blocks.go b/vms/platformvm/common_blocks.go index 5023a44..9d143f8 100644 --- a/vms/platformvm/common_blocks.go +++ b/vms/platformvm/common_blocks.go @@ -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() diff --git a/vms/platformvm/create_chain_tx.go b/vms/platformvm/create_chain_tx.go index 74bd3f0..67d39a3 100644 --- a/vms/platformvm/create_chain_tx.go +++ b/vms/platformvm/create_chain_tx.go @@ -71,6 +71,9 @@ func (tx *CreateChainTx) Key() crypto.PublicKey { return tx.key } // Bytes returns the byte representation of a CreateChainTx func (tx *CreateChainTx) Bytes() []byte { return tx.bytes } +// InputUTXOs returns an empty set +func (tx *CreateChainTx) 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 *CreateChainTx) SyntacticVerify() error { diff --git a/vms/platformvm/create_subnet_tx.go b/vms/platformvm/create_subnet_tx.go index 0d33ca7..b9c1822 100644 --- a/vms/platformvm/create_subnet_tx.go +++ b/vms/platformvm/create_subnet_tx.go @@ -144,6 +144,9 @@ func (tx *CreateSubnetTx) Bytes() []byte { return tx.bytes } +// InputUTXOs returns an empty set +func (tx *CreateSubnetTx) InputUTXOs() ids.Set { return ids.Set{} } + // initialize sets [tx.vm] to [vm] func (tx *CreateSubnetTx) initialize(vm *VM) error { tx.vm = vm diff --git a/vms/platformvm/standard_block.go b/vms/platformvm/standard_block.go index 847f5c9..6399b98 100644 --- a/vms/platformvm/standard_block.go +++ b/vms/platformvm/standard_block.go @@ -4,16 +4,26 @@ 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") + errConflictingTxs = errors.New("block contains conflicting transactions") +) + // DecisionTx is an operation that can be decided without being proposed type DecisionTx interface { initialize(vm *VM) error + InputUTXOs() ids.Set + // Attempt to verify this transaction with the provided state. The provided // database can be modified arbitrarily. If a nil error is returned, it is // assumped onAccept is non-nil. @@ -26,6 +36,8 @@ type StandardBlock struct { CommonDecisionBlock `serialize:"true"` Txs []DecisionTx `serialize:"true"` + + inputs ids.Set } // initialize this block @@ -41,15 +53,27 @@ func (sb *StandardBlock) initialize(vm *VM, bytes []byte) error { return nil } +// Reject implements the snowman.Block interface +func (sb *StandardBlock) conflicts(s ids.Set) bool { + if sb.Status() == choices.Accepted { + return false + } + if sb.inputs.Overlaps(s) { + return true + } + return sb.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 (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 } @@ -63,11 +87,20 @@ func (sb *StandardBlock) Verify() error { if err != nil { return err } + inputs := tx.InputUTXOs() + if inputs.Overlaps(sb.inputs) { + return errConflictingTxs + } + sb.inputs.Union(inputs) if onAccept != nil { funcs = append(funcs, onAccept) } } + if parentBlock.conflicts(sb.inputs) { + return errConflictingParentTxs + } + if numFuncs := len(funcs); numFuncs == 1 { sb.onAcceptFunc = funcs[0] } else if numFuncs > 1 { From 93ed25f878cf069fac3ac2f9bce8a5ed605cf472 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Wed, 25 Mar 2020 23:48:21 -0400 Subject: [PATCH 18/25] wip --- utils/crypto/secp256k1.go | 3 + utils/crypto/secp256k1r.go | 4 + vms/avm/base_tx.go | 16 +- vms/avm/base_tx_test.go | 972 ++++++++---------- vms/avm/create_asset_tx_test.go | 102 +- vms/avm/export_tx.go | 8 +- vms/avm/export_tx_test.go | 10 +- vms/avm/fx.go | 11 - vms/avm/import_tx.go | 12 +- vms/avm/import_tx_test.go | 6 +- vms/avm/initial_state_test.go | 13 +- vms/avm/operables_test.go | 12 +- vms/avm/operation_test.go | 20 +- vms/avm/prefixed_state_test.go | 34 +- vms/avm/service.go | 56 +- vms/avm/state_test.go | 32 +- vms/avm/tx.go | 4 +- vms/avm/tx_test.go | 72 +- vms/avm/verifiable_test.go | 14 +- vms/avm/vm_test.go | 98 +- vms/components/ava/test_verifiable.go | 20 + vms/{avm => components/ava}/transferables.go | 34 +- .../ava}/transferables_test.go | 59 +- vms/platformvm/atomic_block.go | 150 +++ vms/platformvm/create_chain_tx.go | 3 - vms/platformvm/create_subnet_tx.go | 3 - vms/platformvm/export_tx.go | 168 +++ vms/platformvm/import_tx.go | 252 +++++ vms/platformvm/standard_block.go | 32 - vms/platformvm/vm.go | 19 + xputtest/avmwallet/wallet.go | 52 +- 31 files changed, 1372 insertions(+), 919 deletions(-) create mode 100644 vms/components/ava/test_verifiable.go rename vms/{avm => components/ava}/transferables.go (80%) rename vms/{avm => components/ava}/transferables_test.go (87%) create mode 100644 vms/platformvm/atomic_block.go create mode 100644 vms/platformvm/export_tx.go create mode 100644 vms/platformvm/import_tx.go diff --git a/utils/crypto/secp256k1.go b/utils/crypto/secp256k1.go index 72f4451..b9fa41a 100644 --- a/utils/crypto/secp256k1.go +++ b/utils/crypto/secp256k1.go @@ -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 ... diff --git a/utils/crypto/secp256k1r.go b/utils/crypto/secp256k1r.go index 6de3515..ef50618 100644 --- a/utils/crypto/secp256k1r.go +++ b/utils/crypto/secp256k1r.go @@ -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 ... diff --git a/vms/avm/base_tx.go b/vms/avm/base_tx.go index 911f0a8..0926b9e 100644 --- a/vms/avm/base_tx.go +++ b/vms/avm/base_tx.go @@ -31,10 +31,10 @@ var ( type BaseTx struct { ava.Metadata - NetID uint32 `serialize:"true"` // ID of the network this chain lives on - BCID ids.ID `serialize:"true"` // ID of the chain on which this transaction exists (prevents replay attacks) - Outs []*TransferableOutput `serialize:"true"` // The outputs of this transaction - Ins []*TransferableInput `serialize:"true"` // The inputs to this transaction + NetID uint32 `serialize:"true"` // ID of the network this chain lives on + BCID ids.ID `serialize:"true"` // ID of the chain on which this transaction exists (prevents replay attacks) + Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction + Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction } // NetworkID is the ID of the network on which this transaction exists @@ -45,11 +45,11 @@ 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 } +func (t *BaseTx) Outputs() []*ava.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 } +func (t *BaseTx) Inputs() []*ava.TransferableInput { return t.Ins } // InputUTXOs track which UTXOs this transaction is consuming. func (t *BaseTx) InputUTXOs() []*ava.UTXOID { @@ -104,7 +104,7 @@ func (t *BaseTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error } fc.Produce(out.AssetID(), out.Output().Amount()) } - if !IsSortedTransferableOutputs(t.Outs, c) { + if !ava.IsSortedTransferableOutputs(t.Outs, c) { return errOutputsNotSorted } @@ -114,7 +114,7 @@ func (t *BaseTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error } fc.Consume(in.AssetID(), in.Input().Amount()) } - if !isSortedAndUniqueTransferableInputs(t.Ins) { + if !ava.IsSortedAndUniqueTransferableInputs(t.Ins) { return errInputsNotSortedUnique } diff --git a/vms/avm/base_tx_test.go b/vms/avm/base_tx_test.go index 4d596e5..7b0f7a7 100644 --- a/vms/avm/base_tx_test.go +++ b/vms/avm/base_tx_test.go @@ -78,38 +78,34 @@ func TestBaseTxSerialization(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.Asset{ID: asset}, - Out: &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.NewID([32]byte{ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - }), - OutputIndex: 1, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 54321, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2}, - }, + }}, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.NewID([32]byte{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }), + OutputIndex: 1, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 54321, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2}, }, }, - }, + }}, }} c := codec.NewDefault() @@ -138,38 +134,34 @@ func TestBaseTxGetters(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.Asset{ID: asset}, - Out: &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.NewID([32]byte{ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - }), - OutputIndex: 1, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 54321, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2}, - }, + }}, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.NewID([32]byte{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }), + OutputIndex: 1, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 54321, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2}, }, }, - }, + }}, } tx.Initialize([]byte{}) @@ -218,38 +210,34 @@ func TestBaseTxSyntacticVerify(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.Asset{ID: asset}, - Out: &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.NewID([32]byte{ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - }), - OutputIndex: 0, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 54321, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2}, - }, + }}, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.NewID([32]byte{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }), + OutputIndex: 0, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 54321, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2}, }, }, - }, + }}, } tx.Initialize([]byte{}) @@ -289,38 +277,34 @@ func TestBaseTxSyntacticVerifyWrongNetworkID(t *testing.T) { tx := &BaseTx{ NetID: 0, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.Asset{ID: asset}, - Out: &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.NewID([32]byte{ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - }), - OutputIndex: 1, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 54321, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2}, - }, + }}, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.NewID([32]byte{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }), + OutputIndex: 1, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 54321, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2}, }, }, - }, + }}, } tx.Initialize([]byte{}) @@ -343,38 +327,34 @@ func TestBaseTxSyntacticVerifyWrongChainID(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: ids.Empty, - Outs: []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.Asset{ID: asset}, - Out: &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.NewID([32]byte{ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - }), - OutputIndex: 1, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 54321, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2}, - }, + }}, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.NewID([32]byte{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }), + OutputIndex: 1, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 54321, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2}, }, }, - }, + }}, } tx.Initialize([]byte{}) @@ -397,29 +377,25 @@ func TestBaseTxSyntacticVerifyInvalidOutput(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - nil, - }, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.NewID([32]byte{ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - }), - OutputIndex: 1, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 54321, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2}, - }, + Outs: []*ava.TransferableOutput{nil}, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.NewID([32]byte{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }), + OutputIndex: 1, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 54321, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2}, }, }, - }, + }}, } tx.Initialize([]byte{}) @@ -442,8 +418,8 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ + Outs: []*ava.TransferableOutput{ + &ava.TransferableOutput{ Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 2, @@ -453,7 +429,7 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) { }, }, }, - &TransferableOutput{ + &ava.TransferableOutput{ Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 1, @@ -464,8 +440,8 @@ func TestBaseTxSyntacticVerifyUnsortedOutputs(t *testing.T) { }, }, }, - Ins: []*TransferableInput{ - &TransferableInput{ + Ins: []*ava.TransferableInput{ + &ava.TransferableInput{ UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, @@ -506,21 +482,17 @@ func TestBaseTxSyntacticVerifyInvalidInput(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.Asset{ID: asset}, - Out: &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, - Ins: []*TransferableInput{ - nil, - }, + }}, + Ins: []*ava.TransferableInput{nil}, } tx.Initialize([]byte{}) @@ -543,20 +515,18 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.Asset{ID: asset}, - Out: &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, - Ins: []*TransferableInput{ - &TransferableInput{ + }}, + Ins: []*ava.TransferableInput{ + &ava.TransferableInput{ UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, @@ -574,7 +544,7 @@ func TestBaseTxSyntacticVerifyInputOverflow(t *testing.T) { }, }, }, - &TransferableInput{ + &ava.TransferableInput{ UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, @@ -615,8 +585,8 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ + Outs: []*ava.TransferableOutput{ + &ava.TransferableOutput{ Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 2, @@ -626,7 +596,7 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) { }, }, }, - &TransferableOutput{ + &ava.TransferableOutput{ Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: math.MaxUint64, @@ -637,26 +607,24 @@ func TestBaseTxSyntacticVerifyOutputOverflow(t *testing.T) { }, }, }, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.NewID([32]byte{ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - }), - OutputIndex: 0, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 1, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2}, - }, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.NewID([32]byte{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }), + OutputIndex: 0, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 1, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2}, }, }, - }, + }}, } tx.Initialize([]byte{}) @@ -679,38 +647,34 @@ func TestBaseTxSyntacticVerifyInsufficientFunds(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.Asset{ID: asset}, - Out: &secp256k1fx.TransferOutput{ - Amt: math.MaxUint64, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: math.MaxUint64, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.NewID([32]byte{ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - }), - OutputIndex: 0, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 1, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2}, - }, + }}, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.NewID([32]byte{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }), + OutputIndex: 0, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 1, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2}, }, }, - }, + }}, } tx.Initialize([]byte{}) @@ -733,38 +697,34 @@ func TestBaseTxSyntacticVerifyUninitialized(t *testing.T) { tx := &BaseTx{ NetID: networkID, BCID: chainID, - Outs: []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.Asset{ID: asset}, - Out: &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: asset}, + Out: &secp256k1fx.TransferOutput{ + Amt: 12345, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.NewID([32]byte{ - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, - }), - OutputIndex: 0, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 54321, - Input: secp256k1fx.Input{ - SigIndices: []uint32{2}, - }, + }}, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.NewID([32]byte{ + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, + }), + OutputIndex: 0, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 54321, + Input: secp256k1fx.Input{ + SigIndices: []uint32{2}, }, }, - }, + }}, } if err := tx.SyntacticVerify(ctx, c, 0); err == nil { @@ -801,23 +761,21 @@ func TestBaseTxSemanticVerify(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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(&tx.UnsignedTx) @@ -882,33 +840,31 @@ func TestBaseTxSemanticVerifyUnknownFx(t *testing.T) { } vm.batchTimeout = 0 - vm.codec.RegisterType(&testVerifiable{}) + vm.codec.RegisterType(&ava.TestVerifiable{}) genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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, }, }, }, - }, + }}, }} - tx.Creds = append(tx.Creds, &testVerifiable{}) + tx.Creds = append(tx.Creds, &ava.TestVerifiable{}) b, err := vm.codec.Marshal(tx) if err != nil { @@ -953,30 +909,28 @@ func TestBaseTxSemanticVerifyWrongAssetID(t *testing.T) { } vm.batchTimeout = 0 - vm.codec.RegisterType(&testVerifiable{}) + vm.codec.RegisterType(&ava.TestVerifiable{}) genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: genesisTx.ID(), - OutputIndex: 1, - }, - Asset: ava.Asset{ID: asset}, - 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: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, }, }, }, - }, + }}, }} unsignedBytes, err := vm.codec.Marshal(&tx.UnsignedTx) @@ -1053,23 +1007,21 @@ func TestBaseTxSemanticVerifyUnauthorizedFx(t *testing.T) { codec: vm.codec, } - cr.RegisterType(&TestTransferable{}) + cr.RegisterType(&ava.TestTransferable{}) genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: genesisTx.ID(), - OutputIndex: 1, - }, - Asset: ava.Asset{ID: genesisTx.ID()}, - In: &TestTransferable{}, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: genesisTx.ID(), + OutputIndex: 1, }, - }, + Asset: ava.Asset{ID: genesisTx.ID()}, + In: &ava.TestTransferable{}, + }}, }} unsignedBytes, err := vm.codec.Marshal(&tx.UnsignedTx) @@ -1139,23 +1091,21 @@ func TestBaseTxSemanticVerifyInvalidSignature(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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, }, }, }, - }, + }}, }} tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ @@ -1212,23 +1162,21 @@ func TestBaseTxSemanticVerifyMissingUTXO(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: ids.Empty, - OutputIndex: 1, - }, - Asset: ava.Asset{ID: genesisTx.ID()}, - In: &secp256k1fx.TransferInput{ - Amt: 50000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{ - 0, - }, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: ids.Empty, + OutputIndex: 1, + }, + Asset: ava.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, }, }, }, - }, + }}, }} unsignedBytes, err := vm.codec.Marshal(&tx.UnsignedTx) @@ -1298,23 +1246,21 @@ func TestBaseTxSemanticVerifyInvalidUTXO(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: genesisTx.ID(), - OutputIndex: math.MaxUint32, - }, - Asset: ava.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: math.MaxUint32, + }, + Asset: ava.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, }, }, }, - }, + }}, }} unsignedBytes, err := vm.codec.Marshal(&tx.UnsignedTx) @@ -1383,36 +1329,32 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { pendingTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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: ava.Asset{ID: genesisTx.ID()}, - Out: &secp256k1fx.TransferOutput{ - Amt: 50000, - Locktime: 0, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + }}, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 50000, + Locktime: 0, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, + }}, }} unsignedBytes, err := vm.codec.Marshal(&pendingTx.UnsignedTx) @@ -1456,23 +1398,21 @@ func TestBaseTxSemanticVerifyPendingInvalidUTXO(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: txID, - OutputIndex: 2, - }, - Asset: ava.Asset{ID: genesisTx.ID()}, - In: &secp256k1fx.TransferInput{ - Amt: 50000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{ - 0, - }, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: txID, + OutputIndex: 2, + }, + Asset: ava.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, }, }, }, - }, + }}, }} unsignedBytes, err = vm.codec.Marshal(&tx.UnsignedTx) @@ -1540,36 +1480,32 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { pendingTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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: ava.Asset{ID: genesisTx.ID()}, - Out: &secp256k1fx.TransferOutput{ - Amt: 50000, - Locktime: 0, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + }}, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 50000, + Locktime: 0, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, + }}, }} unsignedBytes, err := vm.codec.Marshal(&pendingTx.UnsignedTx) @@ -1613,23 +1549,21 @@ func TestBaseTxSemanticVerifyPendingWrongAssetID(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: txID, - OutputIndex: 0, - }, - Asset: ava.Asset{ID: asset}, - In: &secp256k1fx.TransferInput{ - Amt: 50000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{ - 0, - }, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: txID, + OutputIndex: 0, + }, + Asset: ava.Asset{ID: asset}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, }, }, }, - }, + }}, }} unsignedBytes, err = vm.codec.Marshal(&tx.UnsignedTx) @@ -1704,43 +1638,39 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { codec: vm.codec, } - cr.RegisterType(&testVerifiable{}) + cr.RegisterType(&ava.TestVerifiable{}) genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) pendingTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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: ava.Asset{ID: genesisTx.ID()}, - Out: &secp256k1fx.TransferOutput{ - Amt: 50000, - Locktime: 0, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + }}, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 50000, + Locktime: 0, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, + }}, }} unsignedBytes, err := vm.codec.Marshal(&pendingTx.UnsignedTx) @@ -1784,26 +1714,24 @@ func TestBaseTxSemanticVerifyPendingUnauthorizedFx(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: txID, - OutputIndex: 0, - }, - Asset: ava.Asset{ID: genesisTx.ID()}, - In: &secp256k1fx.TransferInput{ - Amt: 50000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{ - 0, - }, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: txID, + OutputIndex: 0, + }, + Asset: ava.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, }, }, }, - }, + }}, }} - tx.Creds = append(tx.Creds, &testVerifiable{}) + tx.Creds = append(tx.Creds, &ava.TestVerifiable{}) b, err = vm.codec.Marshal(tx) if err != nil { @@ -1859,43 +1787,39 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { codec: vm.codec, } - cr.RegisterType(&testVerifiable{}) + cr.RegisterType(&ava.TestVerifiable{}) genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) pendingTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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: ava.Asset{ID: genesisTx.ID()}, - Out: &secp256k1fx.TransferOutput{ - Amt: 50000, - Locktime: 0, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + }}, + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: 50000, + Locktime: 0, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }, + }}, }} unsignedBytes, err := vm.codec.Marshal(&pendingTx.UnsignedTx) @@ -1939,23 +1863,21 @@ func TestBaseTxSemanticVerifyPendingInvalidSignature(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &TransferableInput{ - UTXOID: ava.UTXOID{ - TxID: txID, - OutputIndex: 0, - }, - Asset: ava.Asset{ID: genesisTx.ID()}, - In: &secp256k1fx.TransferInput{ - Amt: 50000, - Input: secp256k1fx.Input{ - SigIndices: []uint32{ - 0, - }, + Ins: []*ava.TransferableInput{&ava.TransferableInput{ + UTXOID: ava.UTXOID{ + TxID: txID, + OutputIndex: 0, + }, + Asset: ava.Asset{ID: genesisTx.ID()}, + In: &secp256k1fx.TransferInput{ + Amt: 50000, + Input: secp256k1fx.Input{ + SigIndices: []uint32{ + 0, }, }, }, - }, + }}, }} tx.Creds = append(tx.Creds, &secp256k1fx.Credential{ diff --git a/vms/avm/create_asset_tx_test.go b/vms/avm/create_asset_tx_test.go index 3586b11..3da5a5a 100644 --- a/vms/avm/create_asset_tx_test.go +++ b/vms/avm/create_asset_tx_test.go @@ -93,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: 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, - }), - }, + 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: 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}, - }, + }}, + 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", diff --git a/vms/avm/export_tx.go b/vms/avm/export_tx.go index d44741c..53906a4 100644 --- a/vms/avm/export_tx.go +++ b/vms/avm/export_tx.go @@ -21,8 +21,8 @@ import ( type ExportTx struct { BaseTx `serialize:"true"` - Outs []*TransferableOutput `serialize:"true"` // The outputs of this transaction - Ins []*TransferableInput `serialize:"true"` // The inputs to this transaction + Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction + Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction } // InputUTXOs track which UTXOs this transaction is consuming. @@ -67,7 +67,7 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) } fc.Produce(out.AssetID(), out.Output().Amount()) } - if !IsSortedTransferableOutputs(t.Outs, c) { + if !ava.IsSortedTransferableOutputs(t.Outs, c) { return errOutputsNotSorted } @@ -77,7 +77,7 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) } fc.Consume(in.AssetID(), in.Input().Amount()) } - if !isSortedAndUniqueTransferableInputs(t.Ins) { + if !ava.IsSortedAndUniqueTransferableInputs(t.Ins) { return errInputsNotSortedUnique } diff --git a/vms/avm/export_tx_test.go b/vms/avm/export_tx_test.go index 8c1a486..c95e831 100644 --- a/vms/avm/export_tx_test.go +++ b/vms/avm/export_tx_test.go @@ -72,7 +72,7 @@ func TestExportTxSerialization(t *testing.T) { 0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88, }), }, - Ins: []*TransferableInput{&TransferableInput{ + 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, @@ -156,7 +156,7 @@ func TestIssueExportTx(t *testing.T) { NetID: networkID, BCID: chainID, }, - Ins: []*TransferableInput{&TransferableInput{ + Ins: []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, @@ -167,7 +167,7 @@ func TestIssueExportTx(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []*TransferableOutput{&TransferableOutput{ + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: genesisTx.ID()}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, @@ -287,7 +287,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) { NetID: networkID, BCID: chainID, }, - Ins: []*TransferableInput{&TransferableInput{ + Ins: []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: ava.UTXOID{ TxID: genesisTx.ID(), OutputIndex: 1, @@ -298,7 +298,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - Outs: []*TransferableOutput{&TransferableOutput{ + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: genesisTx.ID()}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, diff --git a/vms/avm/fx.go b/vms/avm/fx.go index cc4d8e4..ddf903f 100644 --- a/vms/avm/fx.go +++ b/vms/avm/fx.go @@ -5,7 +5,6 @@ package avm import ( "github.com/ava-labs/gecko/ids" - "github.com/ava-labs/gecko/vms/components/verify" ) type parsedFx struct { @@ -32,16 +31,6 @@ type Fx interface { 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 { diff --git a/vms/avm/import_tx.go b/vms/avm/import_tx.go index d349cf2..e34d24b 100644 --- a/vms/avm/import_tx.go +++ b/vms/avm/import_tx.go @@ -17,16 +17,12 @@ import ( "github.com/ava-labs/gecko/vms/components/verify" ) -var ( - errNilUTXOID = errors.New("nil utxo ID is not valid") -) - // ImportTx is a transaction that imports an asset from another blockchain. type ImportTx struct { BaseTx `serialize:"true"` - Outs []*TransferableOutput `serialize:"true"` // The outputs of this transaction - Ins []*TransferableInput `serialize:"true"` // The inputs to this transaction + Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction + Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction } // InputUTXOs track which UTXOs this transaction is consuming. @@ -91,7 +87,7 @@ func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) } fc.Produce(out.AssetID(), out.Output().Amount()) } - if !IsSortedTransferableOutputs(t.Outs, c) { + if !ava.IsSortedTransferableOutputs(t.Outs, c) { return errOutputsNotSorted } @@ -101,7 +97,7 @@ func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) } fc.Consume(in.AssetID(), in.Input().Amount()) } - if !isSortedAndUniqueTransferableInputs(t.Ins) { + if !ava.IsSortedAndUniqueTransferableInputs(t.Ins) { return errInputsNotSortedUnique } diff --git a/vms/avm/import_tx_test.go b/vms/avm/import_tx_test.go index 3599850..ec37aec 100644 --- a/vms/avm/import_tx_test.go +++ b/vms/avm/import_tx_test.go @@ -72,7 +72,7 @@ func TestImportTxSerialization(t *testing.T) { 0x99, 0x99, 0x99, 0x99, 0x88, 0x88, 0x88, 0x88, }), }, - Ins: []*TransferableInput{&TransferableInput{ + 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, @@ -165,7 +165,7 @@ func TestIssueImportTx(t *testing.T) { NetID: networkID, BCID: chainID, }, - Ins: []*TransferableInput{&TransferableInput{ + Ins: []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: utxoID, Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ @@ -306,7 +306,7 @@ func TestForceAcceptImportTx(t *testing.T) { NetID: networkID, BCID: chainID, }, - Ins: []*TransferableInput{&TransferableInput{ + Ins: []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: utxoID, Asset: ava.Asset{ID: genesisTx.ID()}, In: &secp256k1fx.TransferInput{ diff --git a/vms/avm/initial_state_test.go b/vms/avm/initial_state_test.go index 267947e..67c4b15 100644 --- a/vms/avm/initial_state_test.go +++ b/vms/avm/initial_state_test.go @@ -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 { diff --git a/vms/avm/operables_test.go b/vms/avm/operables_test.go index 40d52bf..8ab96bb 100644 --- a/vms/avm/operables_test.go +++ b/vms/avm/operables_test.go @@ -27,7 +27,7 @@ func TestOperableInputVerifyNilFx(t *testing.T) { func TestOperableInputVerify(t *testing.T) { oi := &OperableInput{ UTXOID: ava.UTXOID{TxID: ids.Empty}, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, } if err := oi.Verify(); err != nil { t.Fatal(err) @@ -44,28 +44,28 @@ func TestOperableInputSorting(t *testing.T) { TxID: ids.Empty, OutputIndex: 1, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, &OperableInput{ UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{1}), OutputIndex: 1, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, &OperableInput{ UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 0, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, &OperableInput{ UTXOID: ava.UTXOID{ TxID: ids.NewID([32]byte{1}), OutputIndex: 0, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, } if isSortedAndUniqueOperableInputs(ins) { @@ -98,7 +98,7 @@ func TestOperableInputSorting(t *testing.T) { TxID: ids.Empty, OutputIndex: 1, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }) if isSortedAndUniqueOperableInputs(ins) { t.Fatalf("Shouldn't be unique") diff --git a/vms/avm/operation_test.go b/vms/avm/operation_test.go index 3668307..2fbfd84 100644 --- a/vms/avm/operation_test.go +++ b/vms/avm/operation_test.go @@ -53,14 +53,14 @@ func TestOperationVerifyInputsNotSorted(t *testing.T) { TxID: ids.Empty, OutputIndex: 1, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, &OperableInput{ UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 0, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, }, } @@ -71,13 +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: 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 { @@ -90,7 +90,7 @@ func TestOperationVerify(t *testing.T) { op := &Operation{ Asset: ava.Asset{ID: ids.Empty}, Outs: []verify.Verifiable{ - &testVerifiable{}, + &ava.TestVerifiable{}, }, } if err := op.Verify(c); err != nil { @@ -100,7 +100,7 @@ func TestOperationVerify(t *testing.T) { func TestOperationSorting(t *testing.T) { c := codec.NewDefault() - c.RegisterType(&testVerifiable{}) + c.RegisterType(&ava.TestVerifiable{}) ops := []*Operation{ &Operation{ @@ -111,7 +111,7 @@ func TestOperationSorting(t *testing.T) { TxID: ids.Empty, OutputIndex: 1, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, }, }, @@ -123,7 +123,7 @@ func TestOperationSorting(t *testing.T) { TxID: ids.Empty, OutputIndex: 0, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, }, }, @@ -143,7 +143,7 @@ func TestOperationSorting(t *testing.T) { TxID: ids.Empty, OutputIndex: 1, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, }, }) diff --git a/vms/avm/prefixed_state_test.go b/vms/avm/prefixed_state_test.go index e517f96..8a69f91 100644 --- a/vms/avm/prefixed_state_test.go +++ b/vms/avm/prefixed_state_test.go @@ -19,7 +19,7 @@ func TestPrefixedSetsAndGets(t *testing.T) { vm := GenesisVM(t) state := vm.state - vm.codec.RegisterType(&testVerifiable{}) + vm.codec.RegisterType(&ava.TestVerifiable{}) utxo := &ava.UTXO{ UTXOID: ava.UTXOID{ @@ -27,29 +27,27 @@ func TestPrefixedSetsAndGets(t *testing.T) { OutputIndex: 1, }, Asset: ava.Asset{ID: ids.Empty}, - Out: &testVerifiable{}, + Out: &ava.TestVerifiable{}, } tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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) @@ -115,7 +113,7 @@ func TestPrefixedFundingNoAddresses(t *testing.T) { vm := GenesisVM(t) state := vm.state - vm.codec.RegisterType(&testVerifiable{}) + vm.codec.RegisterType(&ava.TestVerifiable{}) utxo := &ava.UTXO{ UTXOID: ava.UTXOID{ @@ -123,7 +121,7 @@ func TestPrefixedFundingNoAddresses(t *testing.T) { OutputIndex: 1, }, Asset: ava.Asset{ID: ids.Empty}, - Out: &testVerifiable{}, + Out: &ava.TestVerifiable{}, } if err := state.FundUTXO(utxo); err != nil { diff --git a/vms/avm/service.go b/vms/avm/service.go index 8174afe..aa454e7 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -219,7 +219,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 } @@ -601,7 +601,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) { @@ -611,7 +611,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 } @@ -621,7 +621,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) } amountSpent = spent - in := &TransferableInput{ + in := &ava.TransferableInput{ UTXOID: utxo.UTXOID, Asset: ava.Asset{ID: assetID}, In: input, @@ -641,38 +641,34 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) SortTransferableInputsWithSigners(ins, keys) - outs := []*TransferableOutput{ - &TransferableOutput{ - Asset: ava.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: ava.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{ @@ -719,7 +715,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) } type innerSortTransferableInputsWithSigners struct { - ins []*TransferableInput + ins []*ava.TransferableInput signers [][]*crypto.PrivateKeySECP256K1R } @@ -744,13 +740,13 @@ func (ins *innerSortTransferableInputsWithSigners) Swap(i, j int) { // SortTransferableInputsWithSigners sorts the inputs and signers based on the // input's utxo ID -func SortTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) { +func SortTransferableInputsWithSigners(ins []*ava.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 { +func IsSortedAndUniqueTransferableInputsWithSigners(ins []*ava.TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) bool { return utils.IsSortedAndUnique(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers}) } diff --git a/vms/avm/state_test.go b/vms/avm/state_test.go index d0d860e..212fc18 100644 --- a/vms/avm/state_test.go +++ b/vms/avm/state_test.go @@ -175,7 +175,7 @@ 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") @@ -187,7 +187,7 @@ func TestStateUTXOs(t *testing.T) { OutputIndex: 1, }, Asset: ava.Asset{ID: ids.Empty}, - Out: &testVerifiable{}, + Out: &ava.TestVerifiable{}, } if err := state.SetUTXO(ids.Empty, utxo); err != nil { @@ -245,7 +245,7 @@ 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") @@ -254,23 +254,21 @@ func TestStateTXs(t *testing.T) { tx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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) diff --git a/vms/avm/tx.go b/vms/avm/tx.go index eedcc5a..365d37c 100644 --- a/vms/avm/tx.go +++ b/vms/avm/tx.go @@ -26,8 +26,8 @@ type UnsignedTx interface { NetworkID() uint32 ChainID() ids.ID - Outputs() []*TransferableOutput - Inputs() []*TransferableInput + Outputs() []*ava.TransferableOutput + Inputs() []*ava.TransferableInput AssetIDs() ids.Set InputUTXOs() []*ava.UTXOID diff --git a/vms/avm/tx_test.go b/vms/avm/tx_test.go index ceece2b..fa32a19 100644 --- a/vms/avm/tx_test.go +++ b/vms/avm/tx_test.go @@ -44,33 +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: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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) @@ -94,14 +90,14 @@ 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: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &TransferableInput{ + Ins: []*ava.TransferableInput{ + &ava.TransferableInput{ UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 0, @@ -116,7 +112,7 @@ func TestTxInvalidUnsignedTx(t *testing.T) { }, }, }, - &TransferableInput{ + &ava.TransferableInput{ UTXOID: ava.UTXOID{ TxID: ids.Empty, OutputIndex: 0, @@ -134,8 +130,8 @@ func TestTxInvalidUnsignedTx(t *testing.T) { }, }, Creds: []verify.Verifiable{ - &testVerifiable{}, - &testVerifiable{}, + &ava.TestVerifiable{}, + &ava.TestVerifiable{}, }, } @@ -160,27 +156,25 @@ 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: 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, - }, + 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{ @@ -191,15 +185,13 @@ func TestTxInvalidNumberOfCredentials(t *testing.T) { TxID: ids.Empty, OutputIndex: 1, }, - In: &testVerifiable{}, + In: &ava.TestVerifiable{}, }, }, }, }, }, - Creds: []verify.Verifiable{ - &testVerifiable{}, - }, + Creds: []verify.Verifiable{&ava.TestVerifiable{}}, } b, err := c.Marshal(tx) diff --git a/vms/avm/verifiable_test.go b/vms/avm/verifiable_test.go index 65630d2..6e6337a 100644 --- a/vms/avm/verifiable_test.go +++ b/vms/avm/verifiable_test.go @@ -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"` } diff --git a/vms/avm/vm_test.go b/vms/avm/vm_test.go index 1e625e7..2f73e19 100644 --- a/vms/avm/vm_test.go +++ b/vms/avm/vm_test.go @@ -317,7 +317,7 @@ func TestTxSerialization(t *testing.T) { for _, key := range keys { addr := key.PublicKey().Address() - unsignedTx.Outs = append(unsignedTx.Outs, &TransferableOutput{ + unsignedTx.Outs = append(unsignedTx.Outs, &ava.TransferableOutput{ Asset: ava.Asset{ID: asset}, Out: &secp256k1fx.TransferOutput{ Amt: 20 * units.KiloAva, @@ -444,23 +444,21 @@ func TestIssueTx(t *testing.T) { newTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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) @@ -575,35 +573,31 @@ func TestIssueDependentTx(t *testing.T) { firstTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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: ava.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) @@ -638,23 +632,21 @@ func TestIssueDependentTx(t *testing.T) { secondTx := &Tx{UnsignedTx: &BaseTx{ NetID: networkID, BCID: chainID, - Ins: []*TransferableInput{ - &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, - }, + 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) diff --git a/vms/components/ava/test_verifiable.go b/vms/components/ava/test_verifiable.go new file mode 100644 index 0000000..34dce1d --- /dev/null +++ b/vms/components/ava/test_verifiable.go @@ -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 } diff --git a/vms/avm/transferables.go b/vms/components/ava/transferables.go similarity index 80% rename from vms/avm/transferables.go rename to vms/components/ava/transferables.go index 154396f..7e11a28 100644 --- a/vms/avm/transferables.go +++ b/vms/components/ava/transferables.go @@ -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,7 +9,6 @@ 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" ) @@ -22,15 +21,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 { - ava.Asset `serialize:"true"` + Asset `serialize:"true"` - Out FxTransferable `serialize:"true"` + Out Transferable `serialize:"true"` } // Output returns the feature extension output that this Output is using. -func (out *TransferableOutput) Output() FxTransferable { return out.Out } +func (out *TransferableOutput) Output() Transferable { return out.Out } // Verify implements the verify.Verifiable interface func (out *TransferableOutput) Verify() error { @@ -88,14 +97,14 @@ func IsSortedTransferableOutputs(outs []*TransferableOutput, c codec.Codec) bool // TransferableInput ... type TransferableInput struct { - ava.UTXOID `serialize:"true"` - ava.Asset `serialize:"true"` + UTXOID `serialize:"true"` + Asset `serialize:"true"` - In FxTransferable `serialize:"true"` + In Transferable `serialize:"true"` } // Input returns the feature extension input that this Input is using. -func (in *TransferableInput) Input() FxTransferable { return in.In } +func (in *TransferableInput) Input() Transferable { return in.In } // Verify implements the verify.Verifiable interface func (in *TransferableInput) Verify() error { @@ -127,7 +136,10 @@ 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)) } diff --git a/vms/avm/transferables_test.go b/vms/components/ava/transferables_test.go similarity index 87% rename from vms/avm/transferables_test.go rename to vms/components/ava/transferables_test.go index 82bf7b4..80205a6 100644 --- a/vms/avm/transferables_test.go +++ b/vms/components/ava/transferables_test.go @@ -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,7 +9,6 @@ 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/secp256k1fx" ) @@ -22,7 +21,7 @@ func TestTransferableOutputVerifyNil(t *testing.T) { } func TestTransferableOutputVerifyNilFx(t *testing.T) { - to := &TransferableOutput{Asset: ava.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") } @@ -30,7 +29,7 @@ func TestTransferableOutputVerifyNilFx(t *testing.T) { func TestTransferableOutputVerify(t *testing.T) { to := &TransferableOutput{ - Asset: ava.Asset{ID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, Out: &TestTransferable{Val: 1}, } if err := to.Verify(); err != nil { @@ -47,23 +46,23 @@ func TestTransferableOutputSorting(t *testing.T) { outs := []*TransferableOutput{ &TransferableOutput{ - Asset: ava.Asset{ID: ids.NewID([32]byte{1})}, + Asset: Asset{ID: ids.NewID([32]byte{1})}, Out: &TestTransferable{Val: 1}, }, &TransferableOutput{ - Asset: ava.Asset{ID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, Out: &TestTransferable{Val: 1}, }, &TransferableOutput{ - Asset: ava.Asset{ID: ids.NewID([32]byte{1})}, + Asset: Asset{ID: ids.NewID([32]byte{1})}, Out: &TestTransferable{Val: 0}, }, &TransferableOutput{ - Asset: ava.Asset{ID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, Out: &TestTransferable{Val: 0}, }, &TransferableOutput{ - Asset: ava.Asset{ID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, Out: &TestTransferable{Val: 0}, }, } @@ -115,7 +114,7 @@ func TestTransferableOutputSerialization(t *testing.T) { } out := &TransferableOutput{ - Asset: ava.Asset{ + Asset: Asset{ ID: ids.NewID([32]byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, @@ -165,8 +164,8 @@ func TestTransferableInputVerifyNil(t *testing.T) { func TestTransferableInputVerifyNilFx(t *testing.T) { ti := &TransferableInput{ - UTXOID: ava.UTXOID{TxID: ids.Empty}, - Asset: ava.Asset{ID: ids.Empty}, + UTXOID: UTXOID{TxID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, } if err := ti.Verify(); err == nil { t.Fatalf("Should have errored due to nil transferable fx input") @@ -175,8 +174,8 @@ func TestTransferableInputVerifyNilFx(t *testing.T) { func TestTransferableInputVerify(t *testing.T) { ti := &TransferableInput{ - UTXOID: ava.UTXOID{TxID: ids.Empty}, - Asset: ava.Asset{ID: ids.Empty}, + UTXOID: UTXOID{TxID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, In: &TestTransferable{}, } if err := ti.Verify(); err != nil { @@ -193,57 +192,57 @@ func TestTransferableInputSorting(t *testing.T) { ins := []*TransferableInput{ &TransferableInput{ - UTXOID: ava.UTXOID{ + UTXOID: UTXOID{ TxID: ids.NewID([32]byte{1}), OutputIndex: 1, }, - Asset: ava.Asset{ID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, In: &TestTransferable{}, }, &TransferableInput{ - UTXOID: ava.UTXOID{ + UTXOID: UTXOID{ TxID: ids.NewID([32]byte{1}), OutputIndex: 0, }, - Asset: ava.Asset{ID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, In: &TestTransferable{}, }, &TransferableInput{ - UTXOID: ava.UTXOID{ + UTXOID: UTXOID{ TxID: ids.Empty, OutputIndex: 1, }, - Asset: ava.Asset{ID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, In: &TestTransferable{}, }, &TransferableInput{ - UTXOID: ava.UTXOID{ + UTXOID: UTXOID{ TxID: ids.Empty, OutputIndex: 0, }, - Asset: ava.Asset{ID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, In: &TestTransferable{}, }, } - 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") } ins = append(ins, &TransferableInput{ - UTXOID: ava.UTXOID{ + UTXOID: UTXOID{ TxID: ids.Empty, OutputIndex: 1, }, - Asset: ava.Asset{ID: ids.Empty}, + Asset: Asset{ID: ids.Empty}, In: &TestTransferable{}, }) - if isSortedAndUniqueTransferableInputs(ins) { + if IsSortedAndUniqueTransferableInputs(ins) { t.Fatalf("Shouldn't be unique") } } @@ -272,7 +271,7 @@ func TestTransferableInputSerialization(t *testing.T) { } in := &TransferableInput{ - UTXOID: ava.UTXOID{ + UTXOID: UTXOID{ TxID: ids.NewID([32]byte{ 0xf1, 0xe1, 0xd1, 0xc1, 0xb1, 0xa1, 0x91, 0x81, 0x71, 0x61, 0x51, 0x41, 0x31, 0x21, 0x11, 0x01, @@ -281,7 +280,7 @@ func TestTransferableInputSerialization(t *testing.T) { }), OutputIndex: 5, }, - Asset: ava.Asset{ + Asset: Asset{ ID: ids.NewID([32]byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, diff --git a/vms/platformvm/atomic_block.go b/vms/platformvm/atomic_block.go new file mode 100644 index 0000000..2c33da5 --- /dev/null +++ b/vms/platformvm/atomic_block.go @@ -0,0 +1,150 @@ +// (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 + + // 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() + // 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.inputs = ab.Tx.InputUTXOs() + + if parentBlock.conflicts(ab.inputs) { + return errConflictingParentTxs + } + + 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 +} diff --git a/vms/platformvm/create_chain_tx.go b/vms/platformvm/create_chain_tx.go index 67d39a3..74bd3f0 100644 --- a/vms/platformvm/create_chain_tx.go +++ b/vms/platformvm/create_chain_tx.go @@ -71,9 +71,6 @@ func (tx *CreateChainTx) Key() crypto.PublicKey { return tx.key } // Bytes returns the byte representation of a CreateChainTx func (tx *CreateChainTx) Bytes() []byte { return tx.bytes } -// InputUTXOs returns an empty set -func (tx *CreateChainTx) 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 *CreateChainTx) SyntacticVerify() error { diff --git a/vms/platformvm/create_subnet_tx.go b/vms/platformvm/create_subnet_tx.go index b9c1822..0d33ca7 100644 --- a/vms/platformvm/create_subnet_tx.go +++ b/vms/platformvm/create_subnet_tx.go @@ -144,9 +144,6 @@ func (tx *CreateSubnetTx) Bytes() []byte { return tx.bytes } -// InputUTXOs returns an empty set -func (tx *CreateSubnetTx) InputUTXOs() ids.Set { return ids.Set{} } - // initialize sets [tx.vm] to [vm] func (tx *CreateSubnetTx) initialize(vm *VM) error { tx.vm = vm diff --git a/vms/platformvm/export_tx.go b/vms/platformvm/export_tx.go new file mode 100644 index 0000000..36ad245 --- /dev/null +++ b/vms/platformvm/export_tx.go @@ -0,0 +1,168 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package platformvm + +// import ( +// "fmt" + +// "github.com/ava-labs/gecko/chains" +// "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/hashing" +// "github.com/ava-labs/gecko/vms/components/ava" +// ) + +// // 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 the transaction fee for this transaction. +// // Currently unused, as there are no tx fees. +// 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 a CreateChainTx +// 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 +// } + +// 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 +// } +// tx.key = key + +// return nil +// } + +// // SemanticVerify this transaction is valid. +// func (tx *ExportTx) SemanticVerify(db database.Database) (func(), error) { +// if err := tx.SyntacticVerify(); err != nil { +// return nil, err +// } + +// currentChains, err := tx.vm.getChains(db) // chains that currently exist +// if err != nil { +// return nil, errDBChains +// } +// for _, chain := range currentChains { +// if chain.ID().Equals(tx.ID()) { +// return nil, fmt.Errorf("chain with ID %s already exists", chain.ID()) +// } +// } +// currentChains = append(currentChains, tx) // add this new chain +// if err := tx.vm.putChains(db, currentChains); err != nil { +// return nil, err +// } + +// // Deduct tx fee from payer's account +// account, err := tx.vm.getAccount(db, tx.Key().Address()) +// if err != nil { +// return nil, err +// } +// account, err = account.Remove(0, tx.Nonce) +// if err != nil { +// return nil, err +// } +// if err := tx.vm.putAccount(db, account); err != nil { +// return nil, err +// } + +// // If this proposal is committed, create the new blockchain using the chain manager +// onAccept := func() { +// chainParams := chains.ChainParameters{ +// ID: tx.ID(), +// GenesisData: tx.GenesisData, +// VMAlias: tx.VMID.String(), +// } +// for _, fxID := range tx.FxIDs { +// chainParams.FxAliases = append(chainParams.FxAliases, fxID.String()) +// } +// // TODO: Not sure how else to make this not nil pointer error during tests +// if tx.vm.ChainManager != nil { +// tx.vm.ChainManager.CreateChain(chainParams) +// } +// } + +// return onAccept, nil +// } + +// func (vm *VM) newExportTx(nonce uint64, genesisData []byte, vmID ids.ID, fxIDs []ids.ID, chainName string, networkID uint32, key *crypto.PrivateKeySECP256K1R) (*ExportTx, error) { +// tx := &CreateChainTx{ +// UnsignedCreateChainTx: UnsignedCreateChainTx{ +// NetworkID: networkID, +// Nonce: nonce, +// GenesisData: genesisData, +// VMID: vmID, +// FxIDs: fxIDs, +// ChainName: chainName, +// }, +// } + +// unsignedIntf := interface{}(&tx.UnsignedCreateChainTx) +// unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction +// if err != nil { +// return nil, err +// } + +// sig, err := key.Sign(unsignedBytes) +// if err != nil { +// return nil, err +// } +// copy(tx.Sig[:], sig) + +// return tx, tx.initialize(vm) +// } diff --git a/vms/platformvm/import_tx.go b/vms/platformvm/import_tx.go new file mode 100644 index 0000000..563f97c --- /dev/null +++ b/vms/platformvm/import_tx.go @@ -0,0 +1,252 @@ +// (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/snow/choices" + "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" +) + +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 for this transaction. + // Currently unused, as there are no tx fees. + Nonce uint64 `serialize:"true"` + + // Account that this transaction is being sent by. This is needed to ensure the Credentials are replay safe. + Account [crypto.SECP256K1RPKLen]byte `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 + 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 } + +// Bytes returns the byte representation of a CreateChainTx +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 + } + + expectedPublicKey, err := tx.vm.factory.ToPublicKey(tx.Account[:]) + if err != nil { + return err + } + + key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:]) + if err != nil { + return err + } + + if !expectedPublicKey.Address().Equals(key.Address()) { + return errPublicKeySignatureMismatch + } + + tx.key = key + return nil +} + +// SemanticVerify this transaction is valid. +func (tx *ImportTx) SemanticVerify(db database.Database) error { + if err := tx.SyntacticVerify(); err != nil { + return err + } + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID) + defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID) + + state := ava.NewPrefixedState(smDB, Codec) + + amount := uint64(0) + for i, in := range tx.Ins { + newAmount, err := math.Add64(in.In.Amount(), amount) + if err != nil { + return err + } + amount = newAmount + + 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(uTx, utxo.Out, in.In, cred); err != nil { + return err + } + } + + // 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 + } + return tx.vm.putAccount(db, account) +} + +// Accept this transaction. +func (tx *ImportTx) Accept(batch database.Batch) error { + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID) + defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID) + + vsmDB := versiondb.New(smDB) + + state := ava.NewPrefixedState(vsmDB, Codec) + for _, in := range tx.Ins { + utxoID := in.UTXOID.InputID() + if _, err := state.AVMUTXO(utxoID); err == nil { + if err := state.SetAVMUTXO(utxoID, nil); err != nil { + return err + } + } else if err := state.SetAVMStatus(utxoID, choices.Accepted); err != nil { + return err + } + } + + sharedBatch, err := vsmDB.CommitBatch() + if err != nil { + return err + } + + return atomic.WriteAll(batch, sharedBatch) +} + +func (vm *VM) newImportTx(nonce uint64, genesisData []byte, vmID ids.ID, fxIDs []ids.ID, chainName string, networkID uint32, key *crypto.PrivateKeySECP256K1R) (*ImportTx, error) { + tx := &CreateChainTx{ + UnsignedCreateChainTx: UnsignedCreateChainTx{ + NetworkID: networkID, + Nonce: nonce, + GenesisData: genesisData, + VMID: vmID, + FxIDs: fxIDs, + ChainName: chainName, + }, + } + + unsignedIntf := interface{}(&tx.UnsignedCreateChainTx) + unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction + if err != nil { + return nil, err + } + + sig, err := key.Sign(unsignedBytes) + if err != nil { + return nil, err + } + copy(tx.Sig[:], sig) + + return tx, tx.initialize(vm) +} diff --git a/vms/platformvm/standard_block.go b/vms/platformvm/standard_block.go index 6399b98..f3c89c9 100644 --- a/vms/platformvm/standard_block.go +++ b/vms/platformvm/standard_block.go @@ -4,26 +4,16 @@ 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") - errConflictingTxs = errors.New("block contains conflicting transactions") -) - // DecisionTx is an operation that can be decided without being proposed type DecisionTx interface { initialize(vm *VM) error - InputUTXOs() ids.Set - // Attempt to verify this transaction with the provided state. The provided // database can be modified arbitrarily. If a nil error is returned, it is // assumped onAccept is non-nil. @@ -36,8 +26,6 @@ type StandardBlock struct { CommonDecisionBlock `serialize:"true"` Txs []DecisionTx `serialize:"true"` - - inputs ids.Set } // initialize this block @@ -53,17 +41,6 @@ func (sb *StandardBlock) initialize(vm *VM, bytes []byte) error { return nil } -// Reject implements the snowman.Block interface -func (sb *StandardBlock) conflicts(s ids.Set) bool { - if sb.Status() == choices.Accepted { - return false - } - if sb.inputs.Overlaps(s) { - return true - } - return sb.parentBlock().conflicts(s) -} - // Verify this block performs a valid state transition. // // The parent block must be a proposal @@ -87,20 +64,11 @@ func (sb *StandardBlock) Verify() error { if err != nil { return err } - inputs := tx.InputUTXOs() - if inputs.Overlaps(sb.inputs) { - return errConflictingTxs - } - sb.inputs.Union(inputs) if onAccept != nil { funcs = append(funcs, onAccept) } } - if parentBlock.conflicts(sb.inputs) { - return errConflictingParentTxs - } - if numFuncs := len(funcs); numFuncs == 1 { sb.onAcceptFunc = funcs[0] } else if numFuncs > 1 { diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index 20eed9d..ea2b4b6 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -26,6 +26,7 @@ import ( "github.com/ava-labs/gecko/utils/wrappers" "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 +109,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 +132,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{}), ) @@ -141,6 +155,11 @@ type VM struct { // The node's chain manager ChainManager chains.Manager + // AVA asset ID + AVA ids.ID + + fx secp256k1fx.Fx + // Used to create and use keys. factory crypto.FactorySECP256K1R diff --git a/xputtest/avmwallet/wallet.go b/xputtest/avmwallet/wallet.go index e0cd127..a8fd6ba 100644 --- a/xputtest/avmwallet/wallet.go +++ b/xputtest/avmwallet/wallet.go @@ -96,7 +96,7 @@ 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 *ava.UTXO) { - out, ok := utxo.Out.(avm.FxTransferable) + out, ok := utxo.Out.(ava.Transferable) if !ok { return } @@ -116,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 { @@ -138,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) { @@ -148,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 } @@ -158,7 +158,7 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) ( } amountSpent = spent - in := &avm.TransferableInput{ + in := &ava.TransferableInput{ UTXOID: utxo.UTXOID, Asset: ava.Asset{ID: assetID}, In: input, @@ -178,41 +178,37 @@ func (w *Wallet) CreateTx(assetID ids.ID, amount uint64, destAddr ids.ShortID) ( avm.SortTransferableInputsWithSigners(ins, keys) - outs := []*avm.TransferableOutput{ - &avm.TransferableOutput{ - Asset: ava.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: ava.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{ From 3efccbf354d4a12203417218b7ea78253d49d388 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 26 Mar 2020 11:27:47 -0400 Subject: [PATCH 19/25] finished first pass of atomic swaps --- vms/avm/service.go | 40 +--- vms/components/ava/transferables.go | 37 +++ vms/platformvm/atomic_block.go | 12 +- vms/platformvm/event_heap_test.go | 8 +- vms/platformvm/export_tx.go | 309 ++++++++++++++------------ vms/platformvm/import_tx.go | 120 ++++++---- vms/platformvm/static_service_test.go | 2 +- vms/platformvm/vm.go | 44 +++- vms/platformvm/vm_test.go | 170 +++++++++++++- 9 files changed, 503 insertions(+), 239 deletions(-) diff --git a/vms/avm/service.go b/vms/avm/service.go index aa454e7..4eacd75 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -8,11 +8,9 @@ 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" @@ -639,7 +637,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) return errInsufficientFunds } - SortTransferableInputsWithSigners(ins, keys) + ava.SortTransferableInputsWithSigners(ins, keys) outs := []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: assetID}, @@ -714,42 +712,6 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) return nil } -type innerSortTransferableInputsWithSigners struct { - ins []*ava.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 []*ava.TransferableInput, signers [][]*crypto.PrivateKeySECP256K1R) { - sort.Sort(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers}) -} - -// IsSortedAndUniqueTransferableInputsWithSigners returns true if the inputs are -// sorted and unique -func IsSortedAndUniqueTransferableInputsWithSigners(ins []*ava.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"` diff --git a/vms/components/ava/transferables.go b/vms/components/ava/transferables.go index 7e11a28..3c5515a 100644 --- a/vms/components/ava/transferables.go +++ b/vms/components/ava/transferables.go @@ -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" ) @@ -143,3 +144,39 @@ func SortTransferableInputs(ins []*TransferableInput) { sort.Sort(innerSortTrans 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}) +} diff --git a/vms/platformvm/atomic_block.go b/vms/platformvm/atomic_block.go index 2c33da5..4af5ff7 100644 --- a/vms/platformvm/atomic_block.go +++ b/vms/platformvm/atomic_block.go @@ -67,6 +67,13 @@ func (ab *AtomicBlock) conflicts(s ids.Set) bool { // 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) @@ -80,11 +87,6 @@ func (ab *AtomicBlock) Verify() error { if err := ab.Tx.SemanticVerify(ab.onAcceptDB); err != nil { return err } - ab.inputs = ab.Tx.InputUTXOs() - - if parentBlock.conflicts(ab.inputs) { - return errConflictingParentTxs - } ab.vm.currentBlocks[ab.ID().Key()] = ab ab.parentBlock().addChild(ab) diff --git a/vms/platformvm/event_heap_test.go b/vms/platformvm/event_heap_test.go index 1a045ad..01dbc44 100644 --- a/vms/platformvm/event_heap_test.go +++ b/vms/platformvm/event_heap_test.go @@ -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 diff --git a/vms/platformvm/export_tx.go b/vms/platformvm/export_tx.go index 36ad245..113bb5a 100644 --- a/vms/platformvm/export_tx.go +++ b/vms/platformvm/export_tx.go @@ -3,166 +3,199 @@ package platformvm -// import ( -// "fmt" +import ( + "errors" -// "github.com/ava-labs/gecko/chains" -// "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/hashing" -// "github.com/ava-labs/gecko/vms/components/ava" -// ) + "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/choices" + "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" +) -// // UnsignedExportTx is an unsigned ExportTx -// type UnsignedExportTx struct { -// // ID of the network this blockchain exists on -// NetworkID uint32 `serialize:"true"` +var ( + errNoExportOutputs = errors.New("no export outputs") + errOutputsNotSorted = errors.New("outputs not sorted") +) -// // Next unused nonce of account paying the transaction fee for this transaction. -// // Currently unused, as there are no tx fees. -// Nonce uint64 `serialize:"true"` +// UnsignedExportTx is an unsigned ExportTx +type UnsignedExportTx struct { + // ID of the network this blockchain exists on + NetworkID uint32 `serialize:"true"` -// Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction -// } + // Next unused nonce of account paying for this transaction. + Nonce uint64 `serialize:"true"` -// // ExportTx exports funds to the AVM -// type ExportTx struct { -// UnsignedExportTx `serialize:"true"` + Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction +} -// Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"` +// ExportTx exports funds to the AVM +type ExportTx struct { + UnsignedExportTx `serialize:"true"` -// vm *VM -// id ids.ID -// key crypto.PublicKey // public key of transaction signer -// bytes []byte -// } + Sig [crypto.SECP256K1RSigLen]byte `serialize:"true"` -// 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 -// } + vm *VM + id ids.ID + key crypto.PublicKey // public key of transaction signer + bytes []byte +} -// // ID of this transaction -// func (tx *ExportTx) ID() ids.ID { return tx.id } +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 +} -// // 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 } +// ID of this transaction +func (tx *ExportTx) ID() ids.ID { return tx.id } -// // Bytes returns the byte representation of a CreateChainTx -// func (tx *ExportTx) Bytes() []byte { return tx.bytes } +// 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 } -// // InputUTXOs returns an empty set -// func (tx *ExportTx) InputUTXOs() ids.Set { return ids.Set{} } +// Bytes returns the byte representation of an ExportTx +func (tx *ExportTx) Bytes() []byte { return tx.bytes } -// // 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 -// } +// InputUTXOs returns an empty set +func (tx *ExportTx) InputUTXOs() ids.Set { return ids.Set{} } -// unsignedIntf := interface{}(&tx.UnsignedImportTx) -// unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr of unsigned tx -// if err != nil { -// return err -// } +// 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 + } -// key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:]) -// if err != nil { -// return err -// } -// tx.key = key + 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 + } -// return nil -// } + unsignedIntf := interface{}(&tx.UnsignedExportTx) + unsignedBytes, err := Codec.Marshal(&unsignedIntf) // byte repr of unsigned tx + if err != nil { + return err + } -// // SemanticVerify this transaction is valid. -// func (tx *ExportTx) SemanticVerify(db database.Database) (func(), error) { -// if err := tx.SyntacticVerify(); err != nil { -// return nil, err -// } + key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:]) + if err != nil { + return err + } -// currentChains, err := tx.vm.getChains(db) // chains that currently exist -// if err != nil { -// return nil, errDBChains -// } -// for _, chain := range currentChains { -// if chain.ID().Equals(tx.ID()) { -// return nil, fmt.Errorf("chain with ID %s already exists", chain.ID()) -// } -// } -// currentChains = append(currentChains, tx) // add this new chain -// if err := tx.vm.putChains(db, currentChains); err != nil { -// return nil, err -// } + tx.key = key + return nil +} -// // Deduct tx fee from payer's account -// account, err := tx.vm.getAccount(db, tx.Key().Address()) -// if err != nil { -// return nil, err -// } -// account, err = account.Remove(0, tx.Nonce) -// if err != nil { -// return nil, err -// } -// if err := tx.vm.putAccount(db, account); err != nil { -// return nil, err -// } +// SemanticVerify this transaction is valid. +func (tx *ExportTx) SemanticVerify(db database.Database) error { + if err := tx.SyntacticVerify(); err != nil { + return err + } -// // If this proposal is committed, create the new blockchain using the chain manager -// onAccept := func() { -// chainParams := chains.ChainParameters{ -// ID: tx.ID(), -// GenesisData: tx.GenesisData, -// VMAlias: tx.VMID.String(), -// } -// for _, fxID := range tx.FxIDs { -// chainParams.FxAliases = append(chainParams.FxAliases, fxID.String()) -// } -// // TODO: Not sure how else to make this not nil pointer error during tests -// if tx.vm.ChainManager != nil { -// tx.vm.ChainManager.CreateChain(chainParams) -// } -// } + amount := uint64(0) + for _, out := range tx.Outs { + newAmount, err := math.Add64(out.Out.Amount(), amount) + if err != nil { + return err + } + amount = newAmount + } -// return onAccept, nil -// } + accountID := tx.key.Address() + account, err := tx.vm.getAccount(db, accountID) + if err != nil { + return errDBAccount + } -// func (vm *VM) newExportTx(nonce uint64, genesisData []byte, vmID ids.ID, fxIDs []ids.ID, chainName string, networkID uint32, key *crypto.PrivateKeySECP256K1R) (*ExportTx, error) { -// tx := &CreateChainTx{ -// UnsignedCreateChainTx: UnsignedCreateChainTx{ -// NetworkID: networkID, -// Nonce: nonce, -// GenesisData: genesisData, -// VMID: vmID, -// FxIDs: fxIDs, -// ChainName: chainName, -// }, -// } + account, err = account.Remove(amount, tx.Nonce) + if err != nil { + return err + } + return tx.vm.putAccount(db, account) +} -// unsignedIntf := interface{}(&tx.UnsignedCreateChainTx) -// unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction -// if err != nil { -// return nil, err -// } +// Accept this transaction. +func (tx *ExportTx) Accept(batch database.Batch) error { + txID := tx.ID() -// sig, err := key.Sign(unsignedBytes) -// if err != nil { -// return nil, err -// } -// copy(tx.Sig[:], sig) + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID) + defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID) -// return tx, tx.initialize(vm) -// } + 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, + } + + utxoID := utxo.InputID() + if _, err := state.PlatformStatus(utxoID); err == nil { + if err := state.SetPlatformStatus(utxoID, choices.Unknown); err != nil { + return err + } + } else if err := state.SetPlatformUTXO(utxoID, utxo); err != nil { + return err + } + } + + sharedBatch, err := vsmDB.CommitBatch() + if err != nil { + return err + } + + return atomic.WriteAll(batch, sharedBatch) +} + +func (vm *VM) newExportTx(nonce uint64, networkID uint32, outs []*ava.TransferableOutput, from *crypto.PrivateKeySECP256K1R) (*ExportTx, error) { + ava.SortTransferableOutputs(outs, Codec) + + tx := &ExportTx{UnsignedExportTx: UnsignedExportTx{ + NetworkID: networkID, + Nonce: nonce, + Outs: outs, + }} + + unsignedIntf := interface{}(&tx.UnsignedExportTx) + unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction + if err != nil { + return nil, err + } + + sig, err := key.Sign(unsignedBytes) + if err != nil { + return nil, err + } + copy(tx.Sig[:], sig) + + return tx, tx.initialize(vm) +} diff --git a/vms/platformvm/import_tx.go b/vms/platformvm/import_tx.go index 563f97c..01a12ce 100644 --- a/vms/platformvm/import_tx.go +++ b/vms/platformvm/import_tx.go @@ -5,6 +5,7 @@ package platformvm import ( "errors" + "fmt" "github.com/ava-labs/gecko/chains/atomic" "github.com/ava-labs/gecko/database" @@ -16,6 +17,7 @@ import ( "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 ( @@ -32,8 +34,8 @@ type UnsignedImportTx struct { // ID of the network this blockchain exists on NetworkID uint32 `serialize:"true"` - // Next unused nonce of account paying the transaction fee for this transaction. - // Currently unused, as there are no tx fees. + // 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. @@ -49,10 +51,11 @@ type ImportTx struct { 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 - bytes []byte + vm *VM + id ids.ID + key crypto.PublicKey // public key of transaction signer + unsignedBytes []byte + bytes []byte } func (tx *ImportTx) initialize(vm *VM) error { @@ -70,7 +73,10 @@ func (tx *ImportTx) ID() ids.ID { return tx.id } // Precondition: tx.Verify() has been called and returned nil func (tx *ImportTx) Key() crypto.PublicKey { return tx.key } -// Bytes returns the byte representation of a CreateChainTx +// 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 @@ -139,6 +145,7 @@ func (tx *ImportTx) SyntacticVerify() error { } tx.key = key + tx.unsignedBytes = unsignedBytes return nil } @@ -148,36 +155,13 @@ func (tx *ImportTx) SemanticVerify(db database.Database) error { return err } - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID) - defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID) - - state := ava.NewPrefixedState(smDB, Codec) - amount := uint64(0) - for i, in := range tx.Ins { + for _, in := range tx.Ins { newAmount, err := math.Add64(in.In.Amount(), amount) if err != nil { return err } amount = newAmount - - 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(uTx, utxo.Out, in.In, cred); err != nil { - return err - } } // Deduct tx fee from payer's account @@ -193,7 +177,36 @@ func (tx *ImportTx) SemanticVerify(db database.Database) error { if err != nil { return err } - return tx.vm.putAccount(db, account) + if err := tx.vm.putAccount(db, account); err != nil { + return err + } + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID) + defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID) + + 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. @@ -224,25 +237,42 @@ func (tx *ImportTx) Accept(batch database.Batch) error { return atomic.WriteAll(batch, sharedBatch) } -func (vm *VM) newImportTx(nonce uint64, genesisData []byte, vmID ids.ID, fxIDs []ids.ID, chainName string, networkID uint32, key *crypto.PrivateKeySECP256K1R) (*ImportTx, error) { - tx := &CreateChainTx{ - UnsignedCreateChainTx: UnsignedCreateChainTx{ - NetworkID: networkID, - Nonce: nonce, - GenesisData: genesisData, - VMID: vmID, - FxIDs: fxIDs, - ChainName: chainName, - }, - } +func (vm *VM) newImportTx(nonce uint64, networkID uint32, ins []*ava.TransferableInput, from [][]*crypto.PrivateKeySECP256K1R, to *crypto.PrivateKeySECP256K1R) (*ImportTx, error) { + ava.SortTransferableInputsWithSigners(ins, from) - unsignedIntf := interface{}(&tx.UnsignedCreateChainTx) + tx := &ImportTx{UnsignedImportTx: UnsignedImportTx{ + NetworkID: networkID, + Nonce: nonce, + Ins: ins, + }} + + pubkeyBytes := key.PublicKey().Bytes() + copy(tx.Account[:], pubkeyBytes) + + unsignedIntf := interface{}(&tx.UnsignedImportTx) unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction if err != nil { return nil, err } - sig, err := key.Sign(unsignedBytes) + hash := hashing.ComputeHash256(unsignedBytes) + + for _, credKeys := range from { + cred := &secp256k1fx.Credential{} + for _, key := range credKeys { + sig, err := key.SignHash(hash) + if err != nil { + return nil, fmt.Errorf("problem creating transaction: %w", err) + } + fixedSig := [crypto.SECP256K1RSigLen]byte{} + copy(fixedSig[:], sig) + + cred.Sigs = append(cred.Sigs, fixedSig) + } + tx.Creds = append(tx.Creds, cred) + } + + sig, err := key.SignHash(hash) if err != nil { return nil, err } diff --git a/vms/platformvm/static_service_test.go b/vms/platformvm/static_service_test.go index d1bdc4e..680460a 100644 --- a/vms/platformvm/static_service_test.go +++ b/vms/platformvm/static_service_test.go @@ -20,7 +20,7 @@ func TestBuildGenesis(t *testing.T) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x5b, 0xcd, 0x15, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x05, 0x01, 0x5c, 0xce, 0x6c, 0x55, 0xd6, 0xb5, + 0x0b, 0x01, 0x5c, 0xce, 0x6c, 0x55, 0xd6, 0xb5, 0x09, 0x84, 0x5c, 0x8c, 0x4e, 0x30, 0xbe, 0xd9, 0x8d, 0x39, 0x1a, 0xe7, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xde, 0x68, 0xb1, 0x00, 0x00, 0x00, diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index ea2b4b6..bb3a173 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -135,8 +135,8 @@ func init() { Codec.RegisterType(&UnsignedImportTx{}), Codec.RegisterType(&ImportTx{}), - // Codec.RegisterType(&UnsignedExportTx{}), - // Codec.RegisterType(&ExportTx{}), + Codec.RegisterType(&UnsignedExportTx{}), + Codec.RegisterType(&ExportTx{}), Codec.RegisterType(&advanceTimeTx{}), Codec.RegisterType(&rewardValidatorTx{}), @@ -158,7 +158,8 @@ type VM struct { // AVA asset ID AVA ids.ID - fx secp256k1fx.Fx + fx secp256k1fx.Fx + codec codec.Codec // Used to create and use keys. factory crypto.FactorySECP256K1R @@ -173,6 +174,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 @@ -200,6 +202,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() @@ -357,6 +365,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) @@ -521,9 +547,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 } @@ -705,3 +731,9 @@ 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 } diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index 67c0084..e4101b5 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/ava-labs/gecko/chains/atomic" "github.com/ava-labs/gecko/database/memdb" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" @@ -18,7 +19,10 @@ import ( "github.com/ava-labs/gecko/snow/validators" "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/formatting" + "github.com/ava-labs/gecko/utils/logging" + "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/core" + "github.com/ava-labs/gecko/vms/secp256k1fx" "github.com/ava-labs/gecko/vms/timestampvm" ) @@ -1058,5 +1062,169 @@ 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() + + utxoID := ava.UTXOID{ + TxID: ids.Empty.Prefix(0), + OutputIndex: 1, + } + assetID := ids.Empty.Prefix(1) + 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.unissuedAtomicTxs = append(vm.unissuedAtomicTxs, tx) + if _, err := vm.BuildBlock(); err == nil { + t.Fatalf("should have errored due to missing utxos") + } + + // Provide the avm UTXO: + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := vm.Ctx.SharedMemory.GetDatabase(bID) + + 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.SetAVMUTXO(utxoID.InputID(), utxo); err != nil { + t.Fatal(err) + } + + vm.Ctx.SharedMemory.ReleaseDatabase(bID) + + 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(bID) + defer vm.Ctx.SharedMemory.ReleaseDatabase(bID) + + 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() + + utxoID := ava.UTXOID{ + TxID: ids.Empty.Prefix(0), + OutputIndex: 1, + } + assetID := ids.Empty.Prefix(1) + 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 + + 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") + } + + bID := ids.Empty // TODO: Needs to be set to the platform chain + smDB := vm.Ctx.SharedMemory.GetDatabase(bID) + defer vm.Ctx.SharedMemory.ReleaseDatabase(bID) + + state := ava.NewPrefixedState(smDB, Codec) + if _, err := state.AVMStatus(utxoID.InputID()); err != nil { + t.Fatal(err) + } } From 87ea21c63fe3e6b5905b92b4a0a694893d30439c Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 26 Mar 2020 18:04:41 -0400 Subject: [PATCH 20/25] linked atomic swaps to main --- genesis/genesis.go | 28 +++++++++++++++++ node/node.go | 7 ++++- vms/avm/export_tx.go | 8 +++-- vms/avm/export_tx_test.go | 50 ++++++++++++++++++------------- vms/avm/factory.go | 12 ++++++-- vms/avm/import_tx.go | 13 ++++---- vms/avm/import_tx_test.go | 34 ++++++++++++--------- vms/avm/unique_tx.go | 1 + vms/avm/vm.go | 3 ++ vms/platformvm/create_chain_tx.go | 4 +-- vms/platformvm/export_tx.go | 7 ++--- vms/platformvm/factory.go | 8 +++-- vms/platformvm/import_tx.go | 12 ++++---- vms/platformvm/service.go | 8 ++--- vms/platformvm/vm.go | 13 ++++---- vms/platformvm/vm_test.go | 34 +++++++++++---------- xputtest/avmwallet/wallet.go | 2 +- 17 files changed, 157 insertions(+), 87 deletions(-) diff --git a/genesis/genesis.go b/genesis/genesis.go index cdf0860..61e2344 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -14,8 +14,10 @@ import ( "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/vms/avm" + "github.com/ava-labs/gecko/vms/components/codec" "github.com/ava-labs/gecko/vms/evm" "github.com/ava-labs/gecko/vms/platformvm" + "github.com/ava-labs/gecko/vms/secp256k1fx" "github.com/ava-labs/gecko/vms/spchainvm" "github.com/ava-labs/gecko/vms/spdagvm" "github.com/ava-labs/gecko/vms/timestampvm" @@ -514,3 +516,29 @@ func VMGenesis(networkID uint32, vmID ids.ID) *platformvm.CreateChainTx { } return nil } + +// AVAAssetID ... +func AVAAssetID(networkID uint32) ids.ID { + createAVM := VMGenesis(networkID, avm.ID) + + c := codec.NewDefault() + c.RegisterType(&avm.BaseTx{}) + c.RegisterType(&avm.CreateAssetTx{}) + c.RegisterType(&avm.OperationTx{}) + c.RegisterType(&avm.ImportTx{}) + c.RegisterType(&avm.ExportTx{}) + c.RegisterType(&secp256k1fx.MintOutput{}) + c.RegisterType(&secp256k1fx.TransferOutput{}) + c.RegisterType(&secp256k1fx.MintInput{}) + c.RegisterType(&secp256k1fx.TransferInput{}) + c.RegisterType(&secp256k1fx.Credential{}) + + genesis := avm.Genesis{} + c.Unmarshal(createAVM.GenesisData, &genesis) + + genesisTx := genesis.Txs[0] + tx := avm.Tx{UnsignedTx: &genesisTx.CreateAssetTx} + txBytes, _ := c.Marshal(&tx) + tx.Initialize(txBytes) + return tx.ID() +} diff --git a/node/node.go b/node/node.go index 9fdc92e..a5c42f0 100644 --- a/node/node.go +++ b/node/node.go @@ -326,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{}) @@ -362,6 +365,8 @@ func (n *Node) initChains() { /*vmFactory=*/ &platformvm.Factory{ ChainManager: n.chainManager, Validators: vdrs, + AVA: genesis.AVAAssetID(n.Config.NetworkID), + AVM: genesis.VMGenesis(n.Config.NetworkID, avm.ID).ID(), }, ) diff --git a/vms/avm/export_tx.go b/vms/avm/export_tx.go index 53906a4..fce9a99 100644 --- a/vms/avm/export_tx.go +++ b/vms/avm/export_tx.go @@ -112,6 +112,9 @@ func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab if !utxoAssetID.Equals(inAssetID) { return errAssetIDMismatch } + if !utxoAssetID.Equals(vm.ava) { + return errWrongAssetID + } if !vm.verifyFxUsage(fxIndex, inAssetID) { return errIncompatibleFx @@ -128,9 +131,8 @@ func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab func (t *ExportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { txID := t.ID() - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := vm.ctx.SharedMemory.GetDatabase(bID) - defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + smDB := vm.ctx.SharedMemory.GetDatabase(vm.platform) + defer vm.ctx.SharedMemory.ReleaseDatabase(vm.platform) vsmDB := versiondb.New(smDB) diff --git a/vms/avm/export_tx_test.go b/vms/avm/export_tx_test.go index c95e831..6bcb2c1 100644 --- a/vms/avm/export_tx_test.go +++ b/vms/avm/export_tx_test.go @@ -130,8 +130,16 @@ func TestIssueExportTx(t *testing.T) { 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{} + vm := &VM{ + ava: avaID, + platform: platformID, + } err := vm.Initialize( ctx, memdb.New(), @@ -149,8 +157,6 @@ func TestIssueExportTx(t *testing.T) { key := keys[0] - genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - tx := &Tx{UnsignedTx: &ExportTx{ BaseTx: BaseTx{ NetID: networkID, @@ -158,17 +164,17 @@ func TestIssueExportTx(t *testing.T) { }, Ins: []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: ava.UTXOID{ - TxID: genesisTx.ID(), + TxID: avaID, OutputIndex: 1, }, - Asset: ava.Asset{ID: genesisTx.ID()}, + 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: genesisTx.ID()}, + Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, OutputOwners: secp256k1fx.OutputOwners{ @@ -228,9 +234,8 @@ func TestIssueExportTx(t *testing.T) { } parsedTx.Accept() - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := vm.ctx.SharedMemory.GetDatabase(bID) - defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + smDB := vm.ctx.SharedMemory.GetDatabase(platformID) + defer vm.ctx.SharedMemory.ReleaseDatabase(platformID) state := ava.NewPrefixedState(smDB, vm.codec) @@ -261,8 +266,16 @@ func TestClearForceAcceptedExportTx(t *testing.T) { 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{} + vm := &VM{ + ava: avaID, + platform: platformID, + } err := vm.Initialize( ctx, memdb.New(), @@ -280,8 +293,6 @@ func TestClearForceAcceptedExportTx(t *testing.T) { key := keys[0] - genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - tx := &Tx{UnsignedTx: &ExportTx{ BaseTx: BaseTx{ NetID: networkID, @@ -289,17 +300,17 @@ func TestClearForceAcceptedExportTx(t *testing.T) { }, Ins: []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: ava.UTXOID{ - TxID: genesisTx.ID(), + TxID: avaID, OutputIndex: 1, }, - Asset: ava.Asset{ID: genesisTx.ID()}, + 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: genesisTx.ID()}, + Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, OutputOwners: secp256k1fx.OutputOwners{ @@ -358,8 +369,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) { t.Fatal(err) } - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := vm.ctx.SharedMemory.GetDatabase(bID) + smDB := vm.ctx.SharedMemory.GetDatabase(platformID) state := ava.NewPrefixedState(smDB, vm.codec) @@ -372,12 +382,12 @@ func TestClearForceAcceptedExportTx(t *testing.T) { t.Fatal(err) } - vm.ctx.SharedMemory.ReleaseDatabase(bID) + vm.ctx.SharedMemory.ReleaseDatabase(platformID) parsedTx.Accept() - smDB = vm.ctx.SharedMemory.GetDatabase(bID) - defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + smDB = vm.ctx.SharedMemory.GetDatabase(platformID) + defer vm.ctx.SharedMemory.ReleaseDatabase(platformID) state = ava.NewPrefixedState(smDB, vm.codec) diff --git a/vms/avm/factory.go b/vms/avm/factory.go index b76606d..b8896ad 100644 --- a/vms/avm/factory.go +++ b/vms/avm/factory.go @@ -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, + } +} diff --git a/vms/avm/import_tx.go b/vms/avm/import_tx.go index e34d24b..ed341be 100644 --- a/vms/avm/import_tx.go +++ b/vms/avm/import_tx.go @@ -112,9 +112,8 @@ func (t *ImportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab return err } - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := vm.ctx.SharedMemory.GetDatabase(bID) - defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + smDB := vm.ctx.SharedMemory.GetDatabase(vm.platform) + defer vm.ctx.SharedMemory.ReleaseDatabase(vm.platform) state := ava.NewPrefixedState(smDB, vm.codec) @@ -138,6 +137,9 @@ func (t *ImportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab if !utxoAssetID.Equals(inAssetID) { return errAssetIDMismatch } + if !utxoAssetID.Equals(vm.ava) { + return errWrongAssetID + } if !vm.verifyFxUsage(fxIndex, inAssetID) { return errIncompatibleFx @@ -152,9 +154,8 @@ func (t *ImportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab // ExecuteWithSideEffects writes the batch with any additional side effects func (t *ImportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := vm.ctx.SharedMemory.GetDatabase(bID) - defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + smDB := vm.ctx.SharedMemory.GetDatabase(vm.platform) + defer vm.ctx.SharedMemory.ReleaseDatabase(vm.platform) vsmDB := versiondb.New(smDB) diff --git a/vms/avm/import_tx_test.go b/vms/avm/import_tx_test.go index ec37aec..fb0bcfd 100644 --- a/vms/avm/import_tx_test.go +++ b/vms/avm/import_tx_test.go @@ -130,8 +130,16 @@ func TestIssueImportTx(t *testing.T) { 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{} + vm := &VM{ + ava: avaID, + platform: platformID, + } err := vm.Initialize( ctx, memdb.New(), @@ -149,8 +157,6 @@ func TestIssueImportTx(t *testing.T) { key := keys[0] - genesisTx := GetFirstTxFromGenesisTest(genesisBytes, t) - utxoID := ava.UTXOID{ TxID: ids.NewID([32]byte{ 0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee, @@ -167,7 +173,7 @@ func TestIssueImportTx(t *testing.T) { }, Ins: []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: utxoID, - Asset: ava.Asset{ID: genesisTx.ID()}, + Asset: ava.Asset{ID: avaID}, In: &secp256k1fx.TransferInput{ Amt: 1000, Input: secp256k1fx.Input{SigIndices: []uint32{0}}, @@ -205,12 +211,11 @@ func TestIssueImportTx(t *testing.T) { // Provide the platform UTXO: - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := vm.ctx.SharedMemory.GetDatabase(bID) + smDB := vm.ctx.SharedMemory.GetDatabase(platformID) utxo := &ava.UTXO{ UTXOID: utxoID, - Asset: ava.Asset{ID: genesisTx.ID()}, + Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 1000, OutputOwners: secp256k1fx.OutputOwners{ @@ -225,7 +230,7 @@ func TestIssueImportTx(t *testing.T) { t.Fatal(err) } - vm.ctx.SharedMemory.ReleaseDatabase(bID) + 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) @@ -246,8 +251,8 @@ func TestIssueImportTx(t *testing.T) { parsedTx := txs[0] parsedTx.Accept() - smDB = vm.ctx.SharedMemory.GetDatabase(bID) - defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + 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 { @@ -269,10 +274,12 @@ func TestForceAcceptImportTx(t *testing.T) { ctx.ChainID = chainID ctx.SharedMemory = sm.NewBlockchainSharedMemory(chainID) + platformID := ids.Empty.Prefix(0) + ctx.Lock.Lock() defer ctx.Lock.Unlock() - vm := &VM{} + vm := &VM{platform: platformID} err := vm.Initialize( ctx, memdb.New(), @@ -351,9 +358,8 @@ func TestForceAcceptImportTx(t *testing.T) { parsedTx.Accept() - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := vm.ctx.SharedMemory.GetDatabase(bID) - defer vm.ctx.SharedMemory.ReleaseDatabase(bID) + smDB := vm.ctx.SharedMemory.GetDatabase(platformID) + defer vm.ctx.SharedMemory.ReleaseDatabase(platformID) state := ava.NewPrefixedState(smDB, vm.codec) utxoSource := utxoID.InputID() diff --git a/vms/avm/unique_tx.go b/vms/avm/unique_tx.go index 13673fa..6c4e3f4 100644 --- a/vms/avm/unique_tx.go +++ b/vms/avm/unique_tx.go @@ -14,6 +14,7 @@ import ( 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") diff --git a/vms/avm/vm.go b/vms/avm/vm.go index 0d6a460..543408c 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -50,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 diff --git a/vms/platformvm/create_chain_tx.go b/vms/platformvm/create_chain_tx.go index 74bd3f0..d5287ea 100644 --- a/vms/platformvm/create_chain_tx.go +++ b/vms/platformvm/create_chain_tx.go @@ -148,8 +148,8 @@ func (tx *CreateChainTx) SemanticVerify(db database.Database) (func(), error) { chainParams.FxAliases = append(chainParams.FxAliases, fxID.String()) } // TODO: Not sure how else to make this not nil pointer error during tests - if tx.vm.ChainManager != nil { - tx.vm.ChainManager.CreateChain(chainParams) + if tx.vm.chainManager != nil { + tx.vm.chainManager.CreateChain(chainParams) } } diff --git a/vms/platformvm/export_tx.go b/vms/platformvm/export_tx.go index 113bb5a..f3e3af3 100644 --- a/vms/platformvm/export_tx.go +++ b/vms/platformvm/export_tx.go @@ -86,7 +86,7 @@ func (tx *ExportTx) SyntacticVerify() error { if err := out.Verify(); err != nil { return err } - if !out.AssetID().Equals(tx.vm.AVA) { + if !out.AssetID().Equals(tx.vm.ava) { return errUnknownAsset } } @@ -141,9 +141,8 @@ func (tx *ExportTx) SemanticVerify(db database.Database) error { func (tx *ExportTx) Accept(batch database.Batch) error { txID := tx.ID() - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID) - defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID) + smDB := tx.vm.Ctx.SharedMemory.GetDatabase(tx.vm.avm) + defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(tx.vm.avm) vsmDB := versiondb.New(smDB) diff --git a/vms/platformvm/factory.go b/vms/platformvm/factory.go index 25f9786..eea1467 100644 --- a/vms/platformvm/factory.go +++ b/vms/platformvm/factory.go @@ -18,12 +18,16 @@ var ( type Factory struct { ChainManager chains.Manager Validators validators.Manager + AVA ids.ID + AVM ids.ID } // New returns a new instance of the Platform Chain func (f *Factory) New() interface{} { return &VM{ - ChainManager: f.ChainManager, - Validators: f.Validators, + chainManager: f.ChainManager, + validators: f.Validators, + ava: f.AVA, + avm: f.AVM, } } diff --git a/vms/platformvm/import_tx.go b/vms/platformvm/import_tx.go index 01a12ce..aaa7aad 100644 --- a/vms/platformvm/import_tx.go +++ b/vms/platformvm/import_tx.go @@ -110,7 +110,7 @@ func (tx *ImportTx) SyntacticVerify() error { if err := in.Verify(); err != nil { return err } - if !in.AssetID().Equals(tx.vm.AVA) { + if !in.AssetID().Equals(tx.vm.ava) { return errUnknownAsset } } @@ -181,9 +181,8 @@ func (tx *ImportTx) SemanticVerify(db database.Database) error { return err } - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID) - defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID) + smDB := tx.vm.Ctx.SharedMemory.GetDatabase(tx.vm.avm) + defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(tx.vm.avm) state := ava.NewPrefixedState(smDB, Codec) @@ -211,9 +210,8 @@ func (tx *ImportTx) SemanticVerify(db database.Database) error { // Accept this transaction. func (tx *ImportTx) Accept(batch database.Batch) error { - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := tx.vm.Ctx.SharedMemory.GetDatabase(bID) - defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(bID) + smDB := tx.vm.Ctx.SharedMemory.GetDatabase(tx.vm.avm) + defer tx.vm.Ctx.SharedMemory.ReleaseDatabase(tx.vm.avm) vsmDB := versiondb.New(smDB) diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index c48fe2a..49f492f 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -248,7 +248,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) } @@ -902,14 +902,14 @@ type CreateBlockchainReply struct { // CreateBlockchain issues a transaction to the network to create a new blockchain func (service *Service) CreateBlockchain(_ *http.Request, args *CreateBlockchainArgs, reply *CreateBlockchainReply) error { - vmID, err := service.vm.ChainManager.LookupVM(args.VMID) + vmID, err := service.vm.chainManager.LookupVM(args.VMID) if err != nil { return fmt.Errorf("no VM with ID '%s' found", args.VMID) } fxIDs := []ids.ID(nil) for _, fxIDStr := range args.FxIDs { - fxID, err := service.vm.ChainManager.LookupVM(fxIDStr) + fxID, err := service.vm.chainManager.LookupVM(fxIDStr) if err != nil { return fmt.Errorf("no FX with ID '%s' found", fxIDStr) } @@ -974,7 +974,7 @@ type GetBlockchainStatusReply struct { // GetBlockchainStatus gets the status of a blockchain with the ID [args.BlockchainID]. func (service *Service) GetBlockchainStatus(_ *http.Request, args *GetBlockchainStatusArgs, reply *GetBlockchainStatusReply) error { - _, err := service.vm.ChainManager.Lookup(args.BlockchainID) + _, err := service.vm.chainManager.Lookup(args.BlockchainID) if err == nil { reply.Status = Validating return nil diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index bb3a173..ed2dfdc 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -150,13 +150,16 @@ func init() { type VM struct { *core.SnowmanVM - Validators validators.Manager + validators validators.Manager // The node's chain manager - ChainManager chains.Manager + chainManager chains.Manager // AVA asset ID - AVA ids.ID + ava ids.ID + + // AVM is the ID of the ava virtual machine + avm ids.ID fx secp256k1fx.Fx codec codec.Codec @@ -325,7 +328,7 @@ func (vm *VM) initBlockchains() error { for _, fxID := range chain.FxIDs { chainParams.FxAliases = append(chainParams.FxAliases, fxID.String()) } - vm.ChainManager.CreateChain(chainParams) + vm.chainManager.CreateChain(chainParams) } return nil } @@ -717,7 +720,7 @@ func (vm *VM) getValidators(validatorEvents *EventHeap) []validators.Validator { } func (vm *VM) updateValidators(subnetID ids.ID) error { - validatorSet, ok := vm.Validators.GetValidatorSet(subnetID) + validatorSet, ok := vm.validators.GetValidatorSet(subnetID) if !ok { return fmt.Errorf("couldn't get the validator sampler of the %s subnet", subnetID) } diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index e4101b5..af9d41c 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -120,8 +120,8 @@ func defaultVM() *VM { } defaultSubnet := validators.NewSet() - vm.Validators = validators.NewManager() - vm.Validators.PutValidatorSet(DefaultSubnetID, defaultSubnet) + vm.validators = validators.NewManager() + vm.validators.PutValidatorSet(DefaultSubnetID, defaultSubnet) vm.clock.Set(defaultGenesisTime) db := memdb.New() @@ -1068,11 +1068,12 @@ func TestCreateSubnet(t *testing.T) { func TestAtomicImport(t *testing.T) { vm := defaultVM() + avmID := ids.Empty.Prefix(0) utxoID := ava.UTXOID{ - TxID: ids.Empty.Prefix(0), + TxID: ids.Empty.Prefix(1), OutputIndex: 1, } - assetID := ids.Empty.Prefix(1) + assetID := ids.Empty.Prefix(2) amount := uint64(50000) key := keys[0] @@ -1102,7 +1103,8 @@ func TestAtomicImport(t *testing.T) { vm.Ctx.Lock.Lock() defer vm.Ctx.Lock.Unlock() - vm.AVA = assetID + vm.ava = assetID + vm.avm = avmID vm.unissuedAtomicTxs = append(vm.unissuedAtomicTxs, tx) if _, err := vm.BuildBlock(); err == nil { @@ -1111,8 +1113,7 @@ func TestAtomicImport(t *testing.T) { // Provide the avm UTXO: - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := vm.Ctx.SharedMemory.GetDatabase(bID) + smDB := vm.Ctx.SharedMemory.GetDatabase(avmID) utxo := &ava.UTXO{ UTXOID: utxoID, @@ -1131,7 +1132,7 @@ func TestAtomicImport(t *testing.T) { t.Fatal(err) } - vm.Ctx.SharedMemory.ReleaseDatabase(bID) + vm.Ctx.SharedMemory.ReleaseDatabase(avmID) vm.unissuedAtomicTxs = append(vm.unissuedAtomicTxs, tx) blk, err := vm.BuildBlock() @@ -1145,8 +1146,8 @@ func TestAtomicImport(t *testing.T) { blk.Accept() - smDB = vm.Ctx.SharedMemory.GetDatabase(bID) - defer vm.Ctx.SharedMemory.ReleaseDatabase(bID) + 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 { @@ -1158,11 +1159,12 @@ func TestAtomicImport(t *testing.T) { func TestOptimisticAtomicImport(t *testing.T) { vm := defaultVM() + avmID := ids.Empty.Prefix(0) utxoID := ava.UTXOID{ - TxID: ids.Empty.Prefix(0), + TxID: ids.Empty.Prefix(1), OutputIndex: 1, } - assetID := ids.Empty.Prefix(1) + assetID := ids.Empty.Prefix(2) amount := uint64(50000) key := keys[0] @@ -1192,7 +1194,8 @@ func TestOptimisticAtomicImport(t *testing.T) { vm.Ctx.Lock.Lock() defer vm.Ctx.Lock.Unlock() - vm.AVA = assetID + vm.ava = assetID + vm.avm = avmID blk, err := vm.newAtomicBlock(vm.Preferred(), tx) if err != nil { @@ -1219,9 +1222,8 @@ func TestOptimisticAtomicImport(t *testing.T) { t.Fatalf("failed to provide funds") } - bID := ids.Empty // TODO: Needs to be set to the platform chain - smDB := vm.Ctx.SharedMemory.GetDatabase(bID) - defer vm.Ctx.SharedMemory.ReleaseDatabase(bID) + smDB := vm.Ctx.SharedMemory.GetDatabase(avmID) + defer vm.Ctx.SharedMemory.ReleaseDatabase(avmID) state := ava.NewPrefixedState(smDB, Codec) if _, err := state.AVMStatus(utxoID.InputID()); err != nil { diff --git a/xputtest/avmwallet/wallet.go b/xputtest/avmwallet/wallet.go index a8fd6ba..a94b7fd 100644 --- a/xputtest/avmwallet/wallet.go +++ b/xputtest/avmwallet/wallet.go @@ -176,7 +176,7 @@ 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 := []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: assetID}, From 42b5b137d4f21f3c8e223f3505d08ec132380376 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 26 Mar 2020 18:10:50 -0400 Subject: [PATCH 21/25] updated genesis --- genesis/genesis.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/genesis/genesis.go b/genesis/genesis.go index 61e2344..6e8d191 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -194,7 +194,7 @@ func Genesis(networkID uint32) []byte { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x05, 0xde, 0x31, 0xb4, 0xd8, 0xb2, 0x29, 0x91, + 0x0b, 0xde, 0x31, 0xb4, 0xd8, 0xb2, 0x29, 0x91, 0xd5, 0x1a, 0xa6, 0xaa, 0x1f, 0xc7, 0x33, 0xf2, 0x3a, 0x85, 0x1a, 0x8c, 0x94, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00, 0x00, 0x00, 0x00, @@ -212,7 +212,7 @@ func Genesis(networkID uint32) []byte { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xaa, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xaa, 0x18, 0xd3, 0x99, 0x1c, 0xf6, 0x37, 0xaa, 0x6c, 0x16, 0x2f, 0x5e, 0x95, 0xcf, 0x16, 0x3f, 0x69, 0xcd, 0x82, 0x91, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, @@ -231,7 +231,7 @@ func Genesis(networkID uint32) []byte { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x05, 0xe9, 0x09, 0x4f, 0x73, 0x69, + 0x00, 0x00, 0x0b, 0xe9, 0x09, 0x4f, 0x73, 0x69, 0x80, 0x02, 0xfd, 0x52, 0xc9, 0x08, 0x19, 0xb4, 0x57, 0xb9, 0xfb, 0xc8, 0x66, 0xab, 0x80, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, 0x00, 0x00, @@ -249,7 +249,7 @@ func Genesis(networkID uint32) []byte { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x47, 0x9f, 0x66, 0xc8, 0xbe, 0x89, 0x58, 0x30, 0x54, 0x7e, 0x70, 0xb4, 0xb2, 0x98, 0xca, 0xfd, 0x43, 0x3d, 0xba, 0x6e, 0x00, 0x00, 0x12, 0x30, @@ -268,7 +268,7 @@ func Genesis(networkID uint32) []byte { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x05, 0xf2, 0x9b, 0xce, + 0x00, 0x00, 0x00, 0x00, 0x0b, 0xf2, 0x9b, 0xce, 0x5f, 0x34, 0xa7, 0x43, 0x01, 0xeb, 0x0d, 0xe7, 0x16, 0xd5, 0x19, 0x4e, 0x4a, 0x4a, 0xea, 0x5d, 0x7a, 0x00, 0x00, 0x12, 0x30, 0x9c, 0xe5, 0x40, @@ -308,7 +308,7 @@ func Genesis(networkID uint32) []byte { 0x00, 0x03, 0x41, 0x56, 0x41, 0x00, 0x03, 0x41, 0x56, 0x41, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x04, 0x00, 0x9f, 0xdf, 0x42, 0xf6, + 0x00, 0x00, 0x06, 0x00, 0x9f, 0xdf, 0x42, 0xf6, 0xe4, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x3c, 0xb7, 0xd3, 0x84, 0x2e, From a7f2a887cae7a6fcb73dd759c0f35fc403cdd3d4 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 26 Mar 2020 22:01:22 -0400 Subject: [PATCH 22/25] Updated Export Tx to support change addresses --- vms/avm/export_tx.go | 81 ++++++++++++++++----------------------- vms/avm/export_tx_test.go | 74 +++++++++++++++++------------------ 2 files changed, 68 insertions(+), 87 deletions(-) diff --git a/vms/avm/export_tx.go b/vms/avm/export_tx.go index fce9a99..27d9e40 100644 --- a/vms/avm/export_tx.go +++ b/vms/avm/export_tx.go @@ -4,12 +4,9 @@ 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/snow/choices" "github.com/ava-labs/gecko/vms/components/ava" @@ -17,47 +14,22 @@ import ( "github.com/ava-labs/gecko/vms/components/verify" ) -// ExportTx is a transaction that exports an asset to another blockchain. +// ExportTx is the basis of all transactions. type ExportTx struct { BaseTx `serialize:"true"` - Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction - Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction + ExportOuts []*ava.TransferableOutput `serialize:"true"` // The outputs this transaction is sending to the other chain } -// InputUTXOs track which UTXOs this transaction is consuming. -func (t *ExportTx) InputUTXOs() []*ava.UTXOID { - utxos := t.BaseTx.InputUTXOs() - for _, in := range t.Ins { - utxos = append(utxos, &in.UTXOID) - } - return utxos -} - -// AssetIDs returns the IDs of the assets this transaction depends on -func (t *ExportTx) AssetIDs() ids.Set { - assets := t.BaseTx.AssetIDs() - for _, in := range t.Ins { - assets.Add(in.AssetID()) - } - return assets -} - -var ( - errNoExportInputs = errors.New("no export inputs") -) - // SyntacticVerify that this transaction is well-formed. -func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error { +func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) error { switch { case t == nil: return errNilTx - case len(t.Ins) == 0: - return errNoExportInputs - } - - if err := t.BaseTx.SyntacticVerify(ctx, c, numFxs); err != nil { - return err + case t.NetID != ctx.NetworkID: + return errWrongNetworkID + case !t.BCID.Equals(ctx.ChainID): + return errWrongChainID } fc := ava.NewFlowChecker() @@ -71,6 +43,16 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) return errOutputsNotSorted } + for _, out := range t.ExportOuts { + if err := out.Verify(); err != nil { + return err + } + fc.Produce(out.AssetID(), out.Output().Amount()) + } + if !ava.IsSortedTransferableOutputs(t.ExportOuts, c) { + return errOutputsNotSorted + } + for _, in := range t.Ins { if err := in.Verify(); err != nil { return err @@ -83,18 +65,17 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) // TODO: Add the Tx fee to the produced side - return fc.Verify() -} - -// SemanticVerify that this transaction is well-formed. -func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error { - if err := t.BaseTx.SemanticVerify(vm, uTx, creds); err != nil { + if err := fc.Verify(); err != nil { return err } - offset := len(t.BaseTx.Ins) + 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+offset] + cred := creds[i] fxIndex, err := vm.getFx(cred) if err != nil { @@ -112,9 +93,6 @@ func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab if !utxoAssetID.Equals(inAssetID) { return errAssetIDMismatch } - if !utxoAssetID.Equals(vm.ava) { - return errWrongAssetID - } if !vm.verifyFxUsage(fxIndex, inAssetID) { return errIncompatibleFx @@ -124,6 +102,13 @@ func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab return err } } + + for _, out := range t.ExportOuts { + if !out.AssetID().Equals(vm.ava) { + return errWrongAssetID + } + } + return nil } @@ -137,11 +122,11 @@ func (t *ExportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { vsmDB := versiondb.New(smDB) state := ava.NewPrefixedState(vsmDB, vm.codec) - for i, out := range t.Outs { + for i, out := range t.ExportOuts { utxo := &ava.UTXO{ UTXOID: ava.UTXOID{ TxID: txID, - OutputIndex: uint32(len(t.BaseTx.Outs) + i), + OutputIndex: uint32(len(t.Outs) + i), }, Asset: ava.Asset{ID: out.AssetID()}, Out: out.Out, diff --git a/vms/avm/export_tx_test.go b/vms/avm/export_tx_test.go index 6bcb2c1..008df4f 100644 --- a/vms/avm/export_tx_test.go +++ b/vms/avm/export_tx_test.go @@ -31,10 +31,6 @@ func TestExportTxSerialization(t *testing.T) { 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 outs: 0x00, 0x00, 0x00, 0x00, // number of inputs: @@ -60,18 +56,18 @@ func TestExportTxSerialization(t *testing.T) { 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, - }), - }, + 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, @@ -90,7 +86,7 @@ func TestExportTxSerialization(t *testing.T) { Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, - }} + }}} c := codec.NewDefault() c.RegisterType(&BaseTx{}) @@ -161,19 +157,19 @@ func TestIssueExportTx(t *testing.T) { 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}}, + }, + }}, }, - 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{ + ExportOuts: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, @@ -297,19 +293,19 @@ func TestClearForceAcceptedExportTx(t *testing.T) { 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}}, + }, + }}, }, - 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{ + ExportOuts: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, From 9da52e70c05ea3b04f0e5616fe668dbdf0365980 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Thu, 26 Mar 2020 23:11:23 -0400 Subject: [PATCH 23/25] Add address tracking to atomic utxos --- vms/avm/export_tx.go | 9 +- vms/avm/export_tx_test.go | 9 +- vms/avm/fx.go | 6 - vms/avm/import_tx.go | 7 +- vms/avm/import_tx_test.go | 7 +- vms/avm/prefixed_state.go | 4 +- vms/avm/service.go | 173 ++++++++++++++++ vms/avm/state.go | 40 ---- vms/components/ava/prefixed_state.go | 185 +++++++++++++++--- vms/components/ava/state.go | 40 ++++ .../add_default_subnet_delegator_tx_test.go | 6 +- vms/platformvm/export_tx.go | 9 +- vms/platformvm/import_tx.go | 7 +- vms/platformvm/vm_test.go | 10 +- 14 files changed, 378 insertions(+), 134 deletions(-) diff --git a/vms/avm/export_tx.go b/vms/avm/export_tx.go index 27d9e40..22fef13 100644 --- a/vms/avm/export_tx.go +++ b/vms/avm/export_tx.go @@ -8,7 +8,6 @@ import ( "github.com/ava-labs/gecko/database" "github.com/ava-labs/gecko/database/versiondb" "github.com/ava-labs/gecko/snow" - "github.com/ava-labs/gecko/snow/choices" "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/codec" "github.com/ava-labs/gecko/vms/components/verify" @@ -131,13 +130,7 @@ func (t *ExportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { Asset: ava.Asset{ID: out.AssetID()}, Out: out.Out, } - - utxoID := utxo.InputID() - if _, err := state.AVMStatus(utxoID); err == nil { - if err := state.SetAVMStatus(utxoID, choices.Unknown); err != nil { - return err - } - } else if err := state.SetAVMUTXO(utxoID, utxo); err != nil { + if err := state.FundAVMUTXO(utxo); err != nil { return err } } diff --git a/vms/avm/export_tx_test.go b/vms/avm/export_tx_test.go index 008df4f..a2af503 100644 --- a/vms/avm/export_tx_test.go +++ b/vms/avm/export_tx_test.go @@ -11,7 +11,6 @@ import ( "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/choices" "github.com/ava-labs/gecko/snow/engine/common" "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/logging" @@ -243,9 +242,6 @@ func TestIssueExportTx(t *testing.T) { if _, err := state.AVMUTXO(utxoID); err != nil { t.Fatal(err) } - if _, err := state.AVMStatus(utxoID); err == nil { - t.Fatalf("should have failed to read the status") - } } // Test force accepting an import transaction. @@ -374,7 +370,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) { OutputIndex: 0, } utxoID := utxo.InputID() - if err := state.SetAVMStatus(utxoID, choices.Accepted); err != nil { + if err := state.SpendAVMUTXO(utxoID); err != nil { t.Fatal(err) } @@ -390,7 +386,4 @@ func TestClearForceAcceptedExportTx(t *testing.T) { if _, err := state.AVMUTXO(utxoID); err == nil { t.Fatalf("should have failed to read the utxo") } - if _, err := state.AVMStatus(utxoID); err == nil { - t.Fatalf("should have failed to read the status") - } } diff --git a/vms/avm/fx.go b/vms/avm/fx.go index ddf903f..3121031 100644 --- a/vms/avm/fx.go +++ b/vms/avm/fx.go @@ -30,9 +30,3 @@ type Fx interface { // credential, a non-nil error should be returned. VerifyOperation(tx interface{}, utxos, ins, creds, outs []interface{}) error } - -// 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 -} diff --git a/vms/avm/import_tx.go b/vms/avm/import_tx.go index ed341be..41dc701 100644 --- a/vms/avm/import_tx.go +++ b/vms/avm/import_tx.go @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/gecko/database/versiondb" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" - "github.com/ava-labs/gecko/snow/choices" "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/codec" "github.com/ava-labs/gecko/vms/components/verify" @@ -162,11 +161,7 @@ func (t *ImportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { state := ava.NewPrefixedState(vsmDB, vm.codec) for _, in := range t.Ins { utxoID := in.UTXOID.InputID() - if _, err := state.PlatformUTXO(utxoID); err == nil { - if err := state.SetPlatformUTXO(utxoID, nil); err != nil { - return err - } - } else if err := state.SetPlatformStatus(utxoID, choices.Accepted); err != nil { + if err := state.SpendPlatformUTXO(utxoID); err != nil { return err } } diff --git a/vms/avm/import_tx_test.go b/vms/avm/import_tx_test.go index fb0bcfd..89b5dff 100644 --- a/vms/avm/import_tx_test.go +++ b/vms/avm/import_tx_test.go @@ -11,7 +11,6 @@ import ( "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/choices" "github.com/ava-labs/gecko/snow/engine/common" "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/logging" @@ -226,7 +225,7 @@ func TestIssueImportTx(t *testing.T) { } state := ava.NewPrefixedState(smDB, vm.codec) - if err := state.SetPlatformUTXO(utxoID.InputID(), utxo); err != nil { + if err := state.FundPlatformUTXO(utxo); err != nil { t.Fatal(err) } @@ -365,9 +364,5 @@ func TestForceAcceptImportTx(t *testing.T) { utxoSource := utxoID.InputID() if _, err := state.PlatformUTXO(utxoSource); err == nil { t.Fatalf("shouldn't have been able to read the utxo") - } else if status, err := state.PlatformStatus(utxoSource); err != nil { - t.Fatal(err) - } else if status != choices.Accepted { - t.Fatalf("should have marked the utxo as consumed") } } diff --git a/vms/avm/prefixed_state.go b/vms/avm/prefixed_state.go index e6ca8da..8a1898d 100644 --- a/vms/avm/prefixed_state.go +++ b/vms/avm/prefixed_state.go @@ -95,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 } @@ -124,7 +124,7 @@ func (s *prefixedState) FundUTXO(utxo *ava.UTXO) error { return err } - addressable, ok := utxo.Out.(FxAddressable) + addressable, ok := utxo.Out.(ava.Addressable) if !ok { return nil } diff --git a/vms/avm/service.go b/vms/avm/service.go index 4eacd75..a2a6bb3 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -947,3 +947,176 @@ func (service *Service) SignMintTx(r *http.Request, args *SignMintTxArgs, reply reply.Tx.Bytes = txBytes 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, + }, + ExportOuts: 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 +} diff --git a/vms/avm/state.go b/vms/avm/state.go index 809867a..6033b8b 100644 --- a/vms/avm/state.go +++ b/vms/avm/state.go @@ -63,43 +63,3 @@ func (s *state) SetTx(id ids.ID, tx *Tx) error { s.Cache.Put(id, tx) return s.DB.Put(id.Bytes(), tx.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) -} diff --git a/vms/components/ava/prefixed_state.go b/vms/components/ava/prefixed_state.go index 1cd0c8e..dd7f3e8 100644 --- a/vms/components/ava/prefixed_state.go +++ b/vms/components/ava/prefixed_state.go @@ -8,14 +8,23 @@ import ( "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 ( @@ -23,65 +32,177 @@ const ( 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 { - State - - platformUTXO, platformStatus, avmUTXO, avmStatus cache.Cacher + 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{ - State: State{ - Cache: &cache.LRU{Size: stateCacheSize}, - DB: db, - Codec: codec, + 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: &cache.LRU{Size: idCacheSize}, - platformStatus: &cache.LRU{Size: idCacheSize}, - avmUTXO: &cache.LRU{Size: idCacheSize}, - avmStatus: &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.UTXO(UniqueID(id, platformUTXOID, s.platformUTXO)) + return s.platform.UTXO(id) } -// SetPlatformUTXO saves the provided utxo to platform's storage. -func (s *PrefixedState) SetPlatformUTXO(id ids.ID, utxo *UTXO) error { - return s.SetUTXO(UniqueID(id, platformUTXOID, s.platformUTXO), utxo) +// 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) } -// PlatformStatus returns the platform status from storage. -func (s *PrefixedState) PlatformStatus(id ids.ID) (choices.Status, error) { - return s.Status(UniqueID(id, platformStatusID, s.platformStatus)) +// SpendPlatformUTXO consumes the provided platform utxo. +func (s *PrefixedState) SpendPlatformUTXO(utxoID ids.ID) error { + return s.platform.SpendUTXO(utxoID) } -// SetPlatformStatus saves the provided platform status to storage. -func (s *PrefixedState) SetPlatformStatus(id ids.ID, status choices.Status) error { - return s.SetStatus(UniqueID(id, platformStatusID, s.platformStatus), status) +// 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. +// AVMUTXO attempts to load a utxo from avm's storage. func (s *PrefixedState) AVMUTXO(id ids.ID) (*UTXO, error) { - return s.UTXO(UniqueID(id, avmUTXOID, s.platformUTXO)) + return s.avm.UTXO(id) } -// SetAVMUTXO saves the provided utxo to AVM's storage. -func (s *PrefixedState) SetAVMUTXO(id ids.ID, utxo *UTXO) error { - return s.SetUTXO(UniqueID(id, avmUTXOID, s.platformUTXO), utxo) +// 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) } -// AVMStatus returns the AVM status from storage. -func (s *PrefixedState) AVMStatus(id ids.ID) (choices.Status, error) { - return s.Status(UniqueID(id, avmStatusID, s.platformStatus)) +// SpendAVMUTXO consumes the provided platform utxo. +func (s *PrefixedState) SpendAVMUTXO(utxoID ids.ID) error { + return s.avm.SpendUTXO(utxoID) } -// SetAVMStatus saves the provided platform status to storage. -func (s *PrefixedState) SetAVMStatus(id ids.ID, status choices.Status) error { - return s.SetStatus(UniqueID(id, avmStatusID, s.platformStatus), status) +// FundAVMUTXO adds the provided utxo to the database +func (s *PrefixedState) FundAVMUTXO(utxo *UTXO) error { + return s.avm.FundUTXO(utxo) } diff --git a/vms/components/ava/state.go b/vms/components/ava/state.go index f170d50..a9c5424 100644 --- a/vms/components/ava/state.go +++ b/vms/components/ava/state.go @@ -111,3 +111,43 @@ func (s *State) SetStatus(id ids.ID, status choices.Status) error { } 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) +} diff --git a/vms/platformvm/add_default_subnet_delegator_tx_test.go b/vms/platformvm/add_default_subnet_delegator_tx_test.go index 46ba21e..9d6d5cf 100644 --- a/vms/platformvm/add_default_subnet_delegator_tx_test.go +++ b/vms/platformvm/add_default_subnet_delegator_tx_test.go @@ -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 diff --git a/vms/platformvm/export_tx.go b/vms/platformvm/export_tx.go index f3e3af3..e19f482 100644 --- a/vms/platformvm/export_tx.go +++ b/vms/platformvm/export_tx.go @@ -10,7 +10,6 @@ import ( "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/utils/crypto" "github.com/ava-labs/gecko/utils/hashing" "github.com/ava-labs/gecko/utils/math" @@ -156,13 +155,7 @@ func (tx *ExportTx) Accept(batch database.Batch) error { Asset: ava.Asset{ID: out.AssetID()}, Out: out.Out, } - - utxoID := utxo.InputID() - if _, err := state.PlatformStatus(utxoID); err == nil { - if err := state.SetPlatformStatus(utxoID, choices.Unknown); err != nil { - return err - } - } else if err := state.SetPlatformUTXO(utxoID, utxo); err != nil { + if err := state.FundPlatformUTXO(utxo); err != nil { return err } } diff --git a/vms/platformvm/import_tx.go b/vms/platformvm/import_tx.go index aaa7aad..b92649f 100644 --- a/vms/platformvm/import_tx.go +++ b/vms/platformvm/import_tx.go @@ -11,7 +11,6 @@ import ( "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/utils/crypto" "github.com/ava-labs/gecko/utils/hashing" "github.com/ava-labs/gecko/utils/math" @@ -218,11 +217,7 @@ func (tx *ImportTx) Accept(batch database.Batch) error { state := ava.NewPrefixedState(vsmDB, Codec) for _, in := range tx.Ins { utxoID := in.UTXOID.InputID() - if _, err := state.AVMUTXO(utxoID); err == nil { - if err := state.SetAVMUTXO(utxoID, nil); err != nil { - return err - } - } else if err := state.SetAVMStatus(utxoID, choices.Accepted); err != nil { + if err := state.SpendAVMUTXO(utxoID); err != nil { return err } } diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index af9d41c..d19b06c 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -1128,7 +1128,7 @@ func TestAtomicImport(t *testing.T) { } state := ava.NewPrefixedState(smDB, Codec) - if err := state.SetAVMUTXO(utxoID.InputID(), utxo); err != nil { + if err := state.FundAVMUTXO(utxo); err != nil { t.Fatal(err) } @@ -1221,12 +1221,4 @@ func TestOptimisticAtomicImport(t *testing.T) { if newAccount.Balance != previousAccount.Balance+amount { t.Fatalf("failed to provide funds") } - - smDB := vm.Ctx.SharedMemory.GetDatabase(avmID) - defer vm.Ctx.SharedMemory.ReleaseDatabase(avmID) - - state := ava.NewPrefixedState(smDB, Codec) - if _, err := state.AVMStatus(utxoID.InputID()); err != nil { - t.Fatal(err) - } } From 65393729d2e0c2986ad77193d304e3cdaa00fe84 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Fri, 27 Mar 2020 02:42:16 -0400 Subject: [PATCH 24/25] Finished first pass of APIs --- snow/engine/avalanche/tx_job.go | 9 +- snow/engine/snowman/block_job.go | 9 +- vms/avm/base_tx.go | 14 - vms/avm/base_tx_test.go | 16 +- vms/avm/export_tx.go | 28 +- vms/avm/export_tx_test.go | 4 +- vms/avm/import_tx.go | 40 +- vms/avm/import_tx_test.go | 2 - vms/avm/service.go | 143 ++++- vms/avm/tx.go | 5 - vms/avm/vm.go | 25 + .../add_nondefault_subnet_validator_tx.go | 2 +- ...add_nondefault_subnet_validator_tx_test.go | 88 +-- vms/platformvm/advance_time_tx.go | 8 +- vms/platformvm/atomic_block.go | 2 + vms/platformvm/create_subnet_tx.go | 38 +- vms/platformvm/import_tx.go | 13 +- vms/platformvm/service.go | 515 +++++++++++++++--- vms/platformvm/standard_block.go | 2 + vms/platformvm/state.go | 6 +- vms/platformvm/vm.go | 28 +- vms/platformvm/vm_test.go | 24 +- 22 files changed, 758 insertions(+), 263 deletions(-) diff --git a/snow/engine/avalanche/tx_job.go b/snow/engine/avalanche/tx_job.go index 0462bd3..f0ffe70 100644 --- a/snow/engine/avalanche/tx_job.go +++ b/snow/engine/avalanche/tx_job.go @@ -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() } diff --git a/snow/engine/snowman/block_job.go b/snow/engine/snowman/block_job.go index aab227f..ec5f4a3 100644 --- a/snow/engine/snowman/block_job.go +++ b/snow/engine/snowman/block_job.go @@ -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() } diff --git a/vms/avm/base_tx.go b/vms/avm/base_tx.go index 0926b9e..a0d2cc4 100644 --- a/vms/avm/base_tx.go +++ b/vms/avm/base_tx.go @@ -37,20 +37,6 @@ type BaseTx struct { Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction } -// NetworkID is the ID of the network on which this transaction exists -func (t *BaseTx) NetworkID() uint32 { return t.NetID } - -// ChainID is the ID of the chain on which this transaction exists -func (t *BaseTx) ChainID() ids.ID { return t.BCID } - -// Outputs track which outputs this transaction is producing. The returned array -// should not be modified. -func (t *BaseTx) Outputs() []*ava.TransferableOutput { return t.Outs } - -// Inputs track which UTXOs this transaction is consuming. The returned array -// should not be modified. -func (t *BaseTx) Inputs() []*ava.TransferableInput { return t.Ins } - // InputUTXOs track which UTXOs this transaction is consuming. func (t *BaseTx) InputUTXOs() []*ava.UTXOID { utxos := []*ava.UTXOID(nil) diff --git a/vms/avm/base_tx_test.go b/vms/avm/base_tx_test.go index 7b0f7a7..3236e16 100644 --- a/vms/avm/base_tx_test.go +++ b/vms/avm/base_tx_test.go @@ -167,19 +167,7 @@ func TestBaseTxGetters(t *testing.T) { txID := tx.ID() - if netID := tx.NetworkID(); netID != networkID { - t.Fatalf("Wrong network ID returned") - } else if bcID := tx.ChainID(); !bcID.Equals(chainID) { - t.Fatalf("Wrong chain ID returned") - } else if outs := tx.Outputs(); len(outs) != 1 { - t.Fatalf("Outputs returned wrong number of outs") - } else if out := outs[0]; out != tx.Outs[0] { - t.Fatalf("Outputs returned wrong output") - } else if ins := tx.Inputs(); len(ins) != 1 { - t.Fatalf("Inputs returned wrong number of ins") - } else if in := ins[0]; in != tx.Ins[0] { - t.Fatalf("Inputs returned wrong input") - } else if assets := tx.AssetIDs(); assets.Len() != 1 { + if assets := tx.AssetIDs(); assets.Len() != 1 { t.Fatalf("Wrong number of assets returned") } else if !assets.Contains(asset) { t.Fatalf("Wrong asset returned") @@ -191,8 +179,6 @@ func TestBaseTxGetters(t *testing.T) { t.Fatalf("Wrong output index returned") } else if assetID := utxo.AssetID(); !assetID.Equals(asset) { t.Fatalf("Wrong asset ID returned") - } else if utxoOut := utxo.Out; utxoOut != out.Out { - t.Fatalf("Wrong output returned") } } diff --git a/vms/avm/export_tx.go b/vms/avm/export_tx.go index 22fef13..31f6860 100644 --- a/vms/avm/export_tx.go +++ b/vms/avm/export_tx.go @@ -17,7 +17,7 @@ import ( type ExportTx struct { BaseTx `serialize:"true"` - ExportOuts []*ava.TransferableOutput `serialize:"true"` // The outputs this transaction is sending to the other chain + Outs []*ava.TransferableOutput `serialize:"true"` // The outputs this transaction is sending to the other chain } // SyntacticVerify that this transaction is well-formed. @@ -32,6 +32,16 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) erro } 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 @@ -42,16 +52,6 @@ func (t *ExportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, _ int) erro return errOutputsNotSorted } - for _, out := range t.ExportOuts { - if err := out.Verify(); err != nil { - return err - } - fc.Produce(out.AssetID(), out.Output().Amount()) - } - if !ava.IsSortedTransferableOutputs(t.ExportOuts, c) { - return errOutputsNotSorted - } - for _, in := range t.Ins { if err := in.Verify(); err != nil { return err @@ -102,7 +102,7 @@ func (t *ExportTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiab } } - for _, out := range t.ExportOuts { + for _, out := range t.Outs { if !out.AssetID().Equals(vm.ava) { return errWrongAssetID } @@ -121,11 +121,11 @@ func (t *ExportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error { vsmDB := versiondb.New(smDB) state := ava.NewPrefixedState(vsmDB, vm.codec) - for i, out := range t.ExportOuts { + for i, out := range t.Outs { utxo := &ava.UTXO{ UTXOID: ava.UTXOID{ TxID: txID, - OutputIndex: uint32(len(t.Outs) + i), + OutputIndex: uint32(len(t.BaseTx.Outs) + i), }, Asset: ava.Asset{ID: out.AssetID()}, Out: out.Out, diff --git a/vms/avm/export_tx_test.go b/vms/avm/export_tx_test.go index a2af503..e2d75a0 100644 --- a/vms/avm/export_tx_test.go +++ b/vms/avm/export_tx_test.go @@ -168,7 +168,7 @@ func TestIssueExportTx(t *testing.T) { }, }}, }, - ExportOuts: []*ava.TransferableOutput{&ava.TransferableOutput{ + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, @@ -301,7 +301,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) { }, }}, }, - ExportOuts: []*ava.TransferableOutput{&ava.TransferableOutput{ + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ Asset: ava.Asset{ID: avaID}, Out: &secp256k1fx.TransferOutput{ Amt: 50000, diff --git a/vms/avm/import_tx.go b/vms/avm/import_tx.go index 41dc701..d47542c 100644 --- a/vms/avm/import_tx.go +++ b/vms/avm/import_tx.go @@ -20,8 +20,7 @@ import ( type ImportTx struct { BaseTx `serialize:"true"` - Outs []*ava.TransferableOutput `serialize:"true"` // The outputs of this transaction - Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction + Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction } // InputUTXOs track which UTXOs this transaction is consuming. @@ -43,25 +42,6 @@ func (t *ImportTx) AssetIDs() ids.Set { return assets } -// UTXOs returns the UTXOs transaction is producing. -func (t *ImportTx) UTXOs() []*ava.UTXO { - txID := t.ID() - utxos := t.BaseTx.UTXOs() - - for _, out := range t.Outs { - utxos = append(utxos, &ava.UTXO{ - UTXOID: ava.UTXOID{ - TxID: txID, - OutputIndex: uint32(len(utxos)), - }, - Asset: ava.Asset{ID: out.AssetID()}, - Out: out.Out, - }) - } - - return utxos -} - var ( errNoImportInputs = errors.New("no import inputs") ) @@ -71,14 +51,14 @@ func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) 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 } - if err := t.BaseTx.SyntacticVerify(ctx, c, numFxs); err != nil { - return err - } - fc := ava.NewFlowChecker() for _, out := range t.Outs { if err := out.Verify(); err != nil { @@ -90,6 +70,16 @@ func (t *ImportTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) 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 diff --git a/vms/avm/import_tx_test.go b/vms/avm/import_tx_test.go index 89b5dff..88fad56 100644 --- a/vms/avm/import_tx_test.go +++ b/vms/avm/import_tx_test.go @@ -34,8 +34,6 @@ func TestImportTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, // number of base inputs: 0x00, 0x00, 0x00, 0x00, - // number of outs: - 0x00, 0x00, 0x00, 0x00, // number of inputs: 0x00, 0x00, 0x00, 0x01, // utxoID: diff --git a/vms/avm/service.go b/vms/avm/service.go index a2a6bb3..9e80b89 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -948,6 +948,147 @@ func (service *Service) SignMintTx(r *http.Request, args *SignMintTxArgs, reply 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"` @@ -1083,7 +1224,7 @@ func (service *Service) SendExport(_ *http.Request, args *SendExportArgs, reply Outs: outs, Ins: ins, }, - ExportOuts: exportOuts, + Outs: exportOuts, }} unsignedBytes, err := service.vm.codec.Marshal(&tx.UnsignedTx) diff --git a/vms/avm/tx.go b/vms/avm/tx.go index 365d37c..fcbeddc 100644 --- a/vms/avm/tx.go +++ b/vms/avm/tx.go @@ -24,11 +24,6 @@ type UnsignedTx interface { ID() ids.ID Bytes() []byte - NetworkID() uint32 - ChainID() ids.ID - Outputs() []*ava.TransferableOutput - Inputs() []*ava.TransferableInput - AssetIDs() ids.Set InputUTXOs() []*ava.UTXOID UTXOs() []*ava.UTXO diff --git a/vms/avm/vm.go b/vms/avm/vm.go index 543408c..909c4a5 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -275,6 +275,31 @@ 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) ([]*ava.UTXO, error) { diff --git a/vms/platformvm/add_nondefault_subnet_validator_tx.go b/vms/platformvm/add_nondefault_subnet_validator_tx.go index 6173950..531570a 100644 --- a/vms/platformvm/add_nondefault_subnet_validator_tx.go +++ b/vms/platformvm/add_nondefault_subnet_validator_tx.go @@ -162,7 +162,7 @@ func (tx *addNonDefaultSubnetValidatorTx) SemanticVerify(db database.Database) ( } var subnet *CreateSubnetTx for _, sn := range subnets { - if sn.ID.Equals(tx.SubnetID()) { + if sn.id.Equals(tx.SubnetID()) { subnet = sn break } diff --git a/vms/platformvm/add_nondefault_subnet_validator_tx_test.go b/vms/platformvm/add_nondefault_subnet_validator_tx_test.go index 2d63f06..c29faf1 100644 --- a/vms/platformvm/add_nondefault_subnet_validator_tx_test.go +++ b/vms/platformvm/add_nondefault_subnet_validator_tx_test.go @@ -28,7 +28,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -48,7 +48,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID+1, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -67,7 +67,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -87,7 +87,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -107,7 +107,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -126,7 +126,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix())-1, defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -147,7 +147,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateStartTime.Add(MinimumStakingDuration).Unix())-1, defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -167,7 +167,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateStartTime.Add(MaximumStakingDuration).Unix())+1, defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -187,7 +187,7 @@ func TestAddNonDefaultSubnetValidatorTxSyntacticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -212,7 +212,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix())+1, defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -235,7 +235,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -245,7 +245,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { } _, _, _, _, err = tx.SemanticVerify(vm.DB) if err != nil { - t.Log(testSubnet1.ID) + t.Log(testSubnet1.id) subnets, err := vm.getSubnets(vm.DB) if err != nil { t.Fatal(err) @@ -253,7 +253,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { if len(subnets) == 0 { t.Fatal("no subnets found") } - t.Logf("subnets[0].ID: %v", subnets[0].ID) + t.Logf("subnets[0].ID: %v", subnets[0].id) t.Fatal(err) } @@ -290,7 +290,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(DSStartTime.Unix()), // start validating non-default subnet before default subnet uint64(DSEndTime.Unix()), pendingDSValidatorID, - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -324,7 +324,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(DSStartTime.Unix())-1, // start validating non-default subnet before default subnet uint64(DSEndTime.Unix()), pendingDSValidatorID, - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -346,7 +346,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(DSStartTime.Unix()), uint64(DSEndTime.Unix())+1, // stop validating non-default subnet after stopping validating default subnet pendingDSValidatorID, - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -368,7 +368,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(DSStartTime.Unix()), // same start time as for default subnet uint64(DSEndTime.Unix()), // same end time as for default subnet pendingDSValidatorID, - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, @@ -389,12 +389,12 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { } tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(newTimestamp.Unix()), // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(newTimestamp.Unix()), // start time uint64(newTimestamp.Add(MinimumStakingDuration).Unix()), // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, // tx fee payer @@ -429,7 +429,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), // start time uint64(defaultValidateEndTime.Unix()), // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, newAcctKey.(*crypto.PrivateKeySECP256K1R), // tx fee payer @@ -451,7 +451,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), // start time uint64(defaultValidateEndTime.Unix()), // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, // tx fee payer @@ -465,7 +465,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { SortByStartTime: false, Txs: []TimedTx{tx}, }, - testSubnet1.ID, + testSubnet1.id, ) // Node with ID nodeIDKey.PublicKey().Address() now validating subnet with ID testSubnet1.ID @@ -475,7 +475,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { uint64(defaultValidateStartTime.Unix()), // start time uint64(defaultValidateEndTime.Unix()), // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, // tx fee payer @@ -494,17 +494,17 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { &EventHeap{ SortByStartTime: false, }, - testSubnet1.ID, + testSubnet1.id, ) // Case 9: Too many signatures tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(defaultGenesisTime.Unix()), // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(defaultGenesisTime.Unix()), // start time uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix())+1, // end time keys[0].PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1], testSubnet1ControlKeys[2]}, defaultKey, // tx fee payer @@ -520,12 +520,12 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { // Case 10: Too few signatures tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(defaultGenesisTime.Unix()), // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(defaultGenesisTime.Unix()), // start time uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix()), // end time keys[0].PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[2]}, defaultKey, // tx fee payer @@ -541,12 +541,12 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { // Case 10: Control Signature from invalid key tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(defaultGenesisTime.Unix()), // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(defaultGenesisTime.Unix()), // start time uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix()), // end time keys[0].PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], keys[3]}, defaultKey, // tx fee payer @@ -563,12 +563,12 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { // Case 11: Proposed validator in pending validator set for subnet // First, add validator to pending validator set of subnet tx, err = vm.newAddNonDefaultSubnetValidatorTx( - defaultNonce+1, // nonce - defaultWeight, // weight - uint64(defaultGenesisTime.Unix())+1, // start time + defaultNonce+1, // nonce + defaultWeight, // weight + uint64(defaultGenesisTime.Unix())+1, // start time uint64(defaultGenesisTime.Add(MinimumStakingDuration).Unix())+1, // end time defaultKey.PublicKey().Address(), // node ID - testSubnet1.ID, // subnet ID + testSubnet1.id, // subnet ID testNetworkID, // network ID []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, // tx fee payer @@ -582,7 +582,7 @@ func TestAddNonDefaultSubnetValidatorTxSemanticVerify(t *testing.T) { SortByStartTime: true, Txs: []TimedTx{tx}, }, - testSubnet1.ID, + testSubnet1.id, ) // Node with ID nodeIDKey.PublicKey().Address() now pending validator for subnet with ID testSubnet1.ID @@ -604,7 +604,7 @@ func TestAddNonDefaultSubnetValidatorMarshal(t *testing.T) { uint64(defaultValidateStartTime.Unix()), uint64(defaultValidateEndTime.Unix()), defaultKey.PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, defaultKey, diff --git a/vms/platformvm/advance_time_tx.go b/vms/platformvm/advance_time_tx.go index b126ec8..17bca78 100644 --- a/vms/platformvm/advance_time_tx.go +++ b/vms/platformvm/advance_time_tx.go @@ -105,15 +105,15 @@ func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Databa return nil, nil, nil, nil, err } for _, subnet := range subnets { - current, pending, err := tx.vm.calculateValidators(db, tx.Timestamp(), subnet.ID) + current, pending, err := tx.vm.calculateValidators(db, tx.Timestamp(), subnet.id) if err != nil { return nil, nil, nil, nil, err } - if err := tx.vm.putCurrentValidators(onCommitDB, current, subnet.ID); err != nil { + if err := tx.vm.putCurrentValidators(onCommitDB, current, subnet.id); err != nil { return nil, nil, nil, nil, err } - if err := tx.vm.putPendingValidators(onCommitDB, pending, subnet.ID); err != nil { + if err := tx.vm.putPendingValidators(onCommitDB, pending, subnet.id); err != nil { return nil, nil, nil, nil, err } } @@ -127,7 +127,7 @@ func (tx *advanceTimeTx) SemanticVerify(db database.Database) (*versiondb.Databa return } for _, subnet := range subnets { - if err := tx.vm.updateValidators(subnet.ID); err != nil { + if err := tx.vm.updateValidators(subnet.id); err != nil { tx.vm.Ctx.Log.Debug("failed to update validators on the default subnet: %s", err) } } diff --git a/vms/platformvm/atomic_block.go b/vms/platformvm/atomic_block.go index 4af5ff7..8b973ad 100644 --- a/vms/platformvm/atomic_block.go +++ b/vms/platformvm/atomic_block.go @@ -21,6 +21,8 @@ var ( type AtomicTx interface { initialize(vm *VM) error + ID() ids.ID + // UTXOs this tx consumes InputUTXOs() ids.Set diff --git a/vms/platformvm/create_subnet_tx.go b/vms/platformvm/create_subnet_tx.go index 0d33ca7..f61e157 100644 --- a/vms/platformvm/create_subnet_tx.go +++ b/vms/platformvm/create_subnet_tx.go @@ -23,12 +23,6 @@ var ( // UnsignedCreateSubnetTx is an unsigned proposal to create a new subnet type UnsignedCreateSubnetTx struct { - // The VM this tx exists within - vm *VM - - // ID is this transaction's ID - ID ids.ID - // NetworkID is the ID of the network this tx was issued on NetworkID uint32 `serialize:"true"` @@ -47,6 +41,12 @@ type UnsignedCreateSubnetTx struct { type CreateSubnetTx struct { UnsignedCreateSubnetTx `serialize:"true"` + // The VM this tx exists within + vm *VM + + // ID is this transaction's ID + id ids.ID + // The public key that signed this transaction // The transaction fee will be paid from the corresponding account // (ie the account whose ID is [key].Address()) @@ -60,6 +60,9 @@ type CreateSubnetTx struct { bytes []byte } +// ID returns the ID of this tx +func (tx *CreateSubnetTx) ID() ids.ID { return tx.id } + // SyntacticVerify nil iff [tx] is syntactically valid. // If [tx] is valid, this method sets [tx.key] func (tx *CreateSubnetTx) SyntacticVerify() error { @@ -68,7 +71,7 @@ func (tx *CreateSubnetTx) SyntacticVerify() error { return errNilTx case tx.key != nil: return nil // Only verify the transaction once - case tx.ID.IsZero(): + case tx.id.IsZero(): return errInvalidID case tx.NetworkID != tx.vm.Ctx.NetworkID: return errWrongNetworkID @@ -106,8 +109,8 @@ func (tx *CreateSubnetTx) SemanticVerify(db database.Database) (func(), error) { } for _, subnet := range subnets { - if subnet.ID.Equals(tx.ID) { - return nil, fmt.Errorf("there is already a subnet with ID %s", tx.ID) + if subnet.id.Equals(tx.id) { + return nil, fmt.Errorf("there is already a subnet with ID %s", tx.id) } } subnets = append(subnets, tx) // add new subnet @@ -152,7 +155,7 @@ func (tx *CreateSubnetTx) initialize(vm *VM) error { return err } tx.bytes = txBytes - tx.ID = ids.NewID(hashing.ComputeHash256Array(txBytes)) + tx.id = ids.NewID(hashing.ComputeHash256Array(txBytes)) return nil } @@ -160,15 +163,12 @@ func (vm *VM) newCreateSubnetTx(networkID uint32, nonce uint64, controlKeys []id threshold uint16, payerKey *crypto.PrivateKeySECP256K1R, ) (*CreateSubnetTx, error) { - tx := &CreateSubnetTx{ - UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{ - vm: vm, - NetworkID: networkID, - Nonce: nonce, - ControlKeys: controlKeys, - Threshold: threshold, - }, - } + tx := &CreateSubnetTx{UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{ + NetworkID: networkID, + Nonce: nonce, + ControlKeys: controlKeys, + Threshold: threshold, + }} unsignedIntf := interface{}(&tx.UnsignedCreateSubnetTx) unsignedBytes, err := Codec.Marshal(&unsignedIntf) diff --git a/vms/platformvm/import_tx.go b/vms/platformvm/import_tx.go index b92649f..24419b2 100644 --- a/vms/platformvm/import_tx.go +++ b/vms/platformvm/import_tx.go @@ -38,7 +38,7 @@ type UnsignedImportTx struct { Nonce uint64 `serialize:"true"` // Account that this transaction is being sent by. This is needed to ensure the Credentials are replay safe. - Account [crypto.SECP256K1RPKLen]byte `serialize:"true"` + Account ids.ShortID `serialize:"true"` Ins []*ava.TransferableInput `serialize:"true"` // The inputs to this transaction } @@ -129,17 +129,12 @@ func (tx *ImportTx) SyntacticVerify() error { return err } - expectedPublicKey, err := tx.vm.factory.ToPublicKey(tx.Account[:]) - if err != nil { - return err - } - key, err := tx.vm.factory.RecoverPublicKey(unsignedBytes, tx.Sig[:]) if err != nil { return err } - if !expectedPublicKey.Address().Equals(key.Address()) { + if !tx.Account.Equals(key.Address()) { return errPublicKeySignatureMismatch } @@ -236,12 +231,10 @@ func (vm *VM) newImportTx(nonce uint64, networkID uint32, ins []*ava.Transferabl tx := &ImportTx{UnsignedImportTx: UnsignedImportTx{ NetworkID: networkID, Nonce: nonce, + Account: key.PublicKey().Address(), Ins: ins, }} - pubkeyBytes := key.PublicKey().Bytes() - copy(tx.Account[:], pubkeyBytes) - unsignedIntf := interface{}(&tx.UnsignedImportTx) unsignedBytes, err := Codec.Marshal(&unsignedIntf) // Byte repr. of unsigned transaction if err != nil { diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 49f492f..a2c8240 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -16,7 +16,11 @@ import ( "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/formatting" + "github.com/ava-labs/gecko/utils/hashing" "github.com/ava-labs/gecko/utils/json" + "github.com/ava-labs/gecko/utils/math" + "github.com/ava-labs/gecko/vms/components/ava" + "github.com/ava-labs/gecko/vms/secp256k1fx" ) var ( @@ -97,7 +101,7 @@ func (service *Service) GetSubnets(_ *http.Request, args *GetSubnetsArgs, respon response.Subnets = make([]APISubnet, len(subnets)) for i, subnet := range subnets { response.Subnets[i] = APISubnet{ - ID: subnet.ID, + ID: subnet.id, ControlKeys: subnet.ControlKeys, Threshold: json.Uint16(subnet.Threshold), } @@ -108,10 +112,10 @@ func (service *Service) GetSubnets(_ *http.Request, args *GetSubnetsArgs, respon idsSet := ids.Set{} idsSet.Add(args.IDs...) for _, subnet := range subnets { - if idsSet.Contains(subnet.ID) { + if idsSet.Contains(subnet.id) { response.Subnets = append(response.Subnets, APISubnet{ - ID: subnet.ID, + ID: subnet.id, ControlKeys: subnet.ControlKeys, Threshold: json.Uint16(subnet.Threshold), }, @@ -432,6 +436,11 @@ type genericTx struct { ****************************************************** */ +// CreateTxResponse is the response from calls to create a transaction +type CreateTxResponse struct { + UnsignedTx formatting.CB58 `json:"unsignedTx"` +} + // AddDefaultSubnetValidatorArgs are the arguments to AddDefaultSubnetValidator type AddDefaultSubnetValidatorArgs struct { APIDefaultSubnetValidator @@ -440,15 +449,9 @@ type AddDefaultSubnetValidatorArgs struct { PayerNonce json.Uint64 `json:"payerNonce"` } -// AddDefaultSubnetValidatorResponse is the response from a call to AddDefaultSubnetValidator -type AddDefaultSubnetValidatorResponse struct { - // The unsigned transaction - UnsignedTx formatting.CB58 `json:"unsignedTx"` -} - // AddDefaultSubnetValidator returns an unsigned transaction to add a validator to the default subnet // The returned unsigned transaction should be signed using Sign() -func (service *Service) AddDefaultSubnetValidator(_ *http.Request, args *AddDefaultSubnetValidatorArgs, reply *AddDefaultSubnetValidatorResponse) error { +func (service *Service) AddDefaultSubnetValidator(_ *http.Request, args *AddDefaultSubnetValidatorArgs, reply *CreateTxResponse) error { service.vm.Ctx.Log.Debug("platform.AddDefaultSubnetValidator called") if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID @@ -490,16 +493,10 @@ type AddDefaultSubnetDelegatorArgs struct { PayerNonce json.Uint64 `json:"payerNonce"` } -// AddDefaultSubnetDelegatorResponse is the response from a call to AddDefaultSubnetDelegator -type AddDefaultSubnetDelegatorResponse struct { - // The unsigned transaction - UnsignedTx formatting.CB58 `json:"unsignedTx"` -} - // AddDefaultSubnetDelegator returns an unsigned transaction to add a delegator // to the default subnet // The returned unsigned transaction should be signed using Sign() -func (service *Service) AddDefaultSubnetDelegator(_ *http.Request, args *AddDefaultSubnetDelegatorArgs, reply *AddDefaultSubnetDelegatorResponse) error { +func (service *Service) AddDefaultSubnetDelegator(_ *http.Request, args *AddDefaultSubnetDelegatorArgs, reply *CreateTxResponse) error { service.vm.Ctx.Log.Debug("platform.AddDefaultSubnetDelegator called") if args.ID.IsZero() { // If ID unspecified, use this node's ID as validator ID @@ -541,15 +538,9 @@ type AddNonDefaultSubnetValidatorArgs struct { PayerNonce json.Uint64 `json:"payerNonce"` } -// AddNonDefaultSubnetValidatorResponse is the response from a call to AddNonDefaultSubnetValidator -type AddNonDefaultSubnetValidatorResponse struct { - // The unsigned transaction - UnsignedTx formatting.CB58 `json:"unsignedTx"` -} - // AddNonDefaultSubnetValidator adds a validator to a subnet other than the default subnet // Returns the unsigned transaction, which must be signed using Sign -func (service *Service) AddNonDefaultSubnetValidator(_ *http.Request, args *AddNonDefaultSubnetValidatorArgs, response *AddNonDefaultSubnetValidatorResponse) error { +func (service *Service) AddNonDefaultSubnetValidator(_ *http.Request, args *AddNonDefaultSubnetValidatorArgs, response *CreateTxResponse) error { tx := addNonDefaultSubnetValidatorTx{ UnsignedAddNonDefaultSubnetValidatorTx: UnsignedAddNonDefaultSubnetValidatorTx{ SubnetValidator: SubnetValidator{ @@ -583,6 +574,83 @@ func (service *Service) AddNonDefaultSubnetValidator(_ *http.Request, args *AddN return nil } +// CreateSubnetArgs are the arguments to CreateSubnet +type CreateSubnetArgs struct { + // The ID member of APISubnet is ignored + APISubnet + + // Nonce of the account that pays the transaction fee + PayerNonce json.Uint64 `json:"payerNonce"` +} + +// CreateSubnet returns an unsigned transaction to create a new subnet. +// The unsigned transaction must be signed with the key of [args.Payer] +func (service *Service) CreateSubnet(_ *http.Request, args *CreateSubnetArgs, response *CreateTxResponse) error { + service.vm.Ctx.Log.Debug("platform.createSubnet called") + + // Create the transaction + tx := CreateSubnetTx{ + UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{ + NetworkID: service.vm.Ctx.NetworkID, + Nonce: uint64(args.PayerNonce), + ControlKeys: args.ControlKeys, + Threshold: uint16(args.Threshold), + }, + key: nil, + Sig: [65]byte{}, + bytes: nil, + } + + txBytes, err := Codec.Marshal(genericTx{Tx: &tx}) + if err != nil { + return errCreatingTransaction + } + + response.UnsignedTx.Bytes = txBytes + return nil +} + +// CreateExportTxArgs are the arguments to CreateExportTx +type CreateExportTxArgs struct { + // ID of the address that will receive the exported funds + To ids.ShortID `json:"to"` + + // Nonce of the account that pays the transaction fee + PayerNonce json.Uint64 `json:"payerNonce"` + + Amount json.Uint64 `json:"amount"` +} + +// CreateExportTx returns an unsigned transaction to export funds. +// The unsigned transaction must be signed with the key of [args.Payer] +func (service *Service) CreateExportTx(_ *http.Request, args *CreateExportTxArgs, response *CreateTxResponse) error { + service.vm.Ctx.Log.Debug("platform.createExportTx called") + + // Create the transaction + tx := ExportTx{UnsignedExportTx: UnsignedExportTx{ + NetworkID: service.vm.Ctx.NetworkID, + Nonce: uint64(args.PayerNonce), + Outs: []*ava.TransferableOutput{&ava.TransferableOutput{ + Asset: ava.Asset{ID: service.vm.ava}, + Out: &secp256k1fx.TransferOutput{ + Amt: uint64(args.Amount), + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{args.To}, + }, + }, + }}, + }} + + txBytes, err := Codec.Marshal(genericTx{Tx: &tx}) + if err != nil { + return errCreatingTransaction + } + + response.UnsignedTx.Bytes = txBytes + return nil +} + /* ****************************************************** **************** Sign/Issue Txs ********************** @@ -606,7 +674,7 @@ type SignArgs struct { // SignResponse is the response from Sign type SignResponse struct { // The signed bytes - Tx formatting.CB58 + Tx formatting.CB58 `json:"tx"` } // Sign [args.bytes] @@ -642,6 +710,8 @@ func (service *Service) Sign(_ *http.Request, args *SignArgs, reply *SignRespons genTx.Tx, err = service.signAddNonDefaultSubnetValidatorTx(tx, key) case *CreateSubnetTx: genTx.Tx, err = service.signCreateSubnetTx(tx, key) + case *ExportTx: + genTx.Tx, err = service.signExportTx(tx, key) default: err = errors.New("Could not parse given tx. Must be one of: addDefaultSubnetValidatorTx, addNonDefaultSubnetValidatorTx, createSubnetTx") } @@ -722,6 +792,29 @@ func (service *Service) signCreateSubnetTx(tx *CreateSubnetTx, key *crypto.Priva return tx, nil } +// Sign [xt] with [key] +func (service *Service) signExportTx(tx *ExportTx, key *crypto.PrivateKeySECP256K1R) (*ExportTx, error) { + service.vm.Ctx.Log.Debug("platform.signAddDefaultSubnetValidatorTx called") + + // TODO: Should we check if tx is already signed? + unsignedIntf := interface{}(&tx.UnsignedExportTx) + unsignedTxBytes, err := Codec.Marshal(&unsignedIntf) + if err != nil { + return nil, fmt.Errorf("error serializing unsigned tx: %v", err) + } + + sig, err := key.Sign(unsignedTxBytes) + if err != nil { + return nil, errors.New("error while signing") + } + if len(sig) != crypto.SECP256K1RSigLen { + return nil, fmt.Errorf("expected signature to be length %d but was length %d", crypto.SECP256K1RSigLen, len(sig)) + } + copy(tx.Sig[:], sig) + + return tx, nil +} + // Signs an unsigned or partially signed addNonDefaultSubnetValidatorTx with [key] // If [key] is a control key for the subnet and there is an empty spot in tx.ControlSigs, signs there // If [key] is a control key for the subnet and there is no empty spot in tx.ControlSigs, signs as payer @@ -775,6 +868,141 @@ func (service *Service) signAddNonDefaultSubnetValidatorTx(tx *addNonDefaultSubn return tx, nil } +// CreateImportTxArgs are the arguments to CreateImportTx +type CreateImportTxArgs struct { + // Addresses that can be used to sign the import + ImportAddresses []ids.ShortID `json:"importAddresses"` + + // ID of the account that will receive the imported funds, and pay the + // import fee + AccountID ids.ShortID `json:"accountID"` + + // Nonce of the account that pays the transaction fee + PayerNonce json.Uint64 `json:"payerNonce"` + + // User that controls the Addresses + Username string `json:"username"` + Password string `json:"password"` +} + +// CreateImportTx returns an unsigned transaction to import funds. +// The unsigned transaction must be signed with the key of [args.Payer] +func (service *Service) CreateImportTx(_ *http.Request, args *CreateImportTxArgs, response *SignResponse) error { + service.vm.Ctx.Log.Debug("platform.createImportTx called") + + // Get the key of the Signer + db, err := service.vm.Ctx.Keystore.GetDatabase(args.Username, args.Password) + if err != nil { + return fmt.Errorf("couldn't get data for user '%s'. Does user exist?", args.Username) + } + user := user{db: db} + + kc := secp256k1fx.NewKeychain() + for _, addr := range args.ImportAddresses { + key, err := user.getKey(addr) + if err != nil { + return errDB + } + kc.Add(key) + } + + key, err := user.getKey(args.AccountID) + if err != nil { + return errDB + } + kc.Add(key) + + addrs := ids.Set{} + for _, addr := range args.ImportAddresses { + addrs.Add(ids.NewID(hashing.ComputeHash256Array(addr.Bytes()))) + } + + utxos, err := service.vm.GetAtomicUTXOs(addrs) + if err != nil { + return fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err) + } + + amount := uint64(0) + time := service.vm.clock.Unix() + + ins := []*ava.TransferableInput{} + keys := [][]*crypto.PrivateKeySECP256K1R{} + for _, utxo := range utxos { + if !utxo.AssetID().Equals(service.vm.ava) { + continue + } + inputIntf, signers, err := kc.Spend(utxo.Out, time) + if err != nil { + continue + } + input, ok := inputIntf.(ava.Transferable) + if !ok { + continue + } + spent, err := math.Add64(amount, input.Amount()) + if err != nil { + return err + } + amount = spent + + in := &ava.TransferableInput{ + UTXOID: utxo.UTXOID, + Asset: ava.Asset{ID: service.vm.ava}, + In: input, + } + + ins = append(ins, in) + keys = append(keys, signers) + } + + ava.SortTransferableInputsWithSigners(ins, keys) + + // Create the transaction + tx := ImportTx{UnsignedImportTx: UnsignedImportTx{ + NetworkID: service.vm.Ctx.NetworkID, + Nonce: uint64(args.PayerNonce), + Account: args.AccountID, + Ins: ins, + }} + + // TODO: Should we check if tx is already signed? + unsignedIntf := interface{}(&tx.UnsignedImportTx) + unsignedTxBytes, err := Codec.Marshal(&unsignedIntf) + if err != nil { + return fmt.Errorf("error serializing unsigned tx: %w", err) + } + hash := hashing.ComputeHash256(unsignedTxBytes) + + sig, err := key.SignHash(unsignedTxBytes) + if err != nil { + return errors.New("error while signing") + } + copy(tx.Sig[:], sig) + + for _, credKeys := range keys { + cred := &secp256k1fx.Credential{} + for _, key := range credKeys { + sig, err := key.SignHash(hash) + if err != nil { + return fmt.Errorf("problem creating transaction: %w", err) + } + fixedSig := [crypto.SECP256K1RSigLen]byte{} + copy(fixedSig[:], sig) + + cred.Sigs = append(cred.Sigs, fixedSig) + } + tx.Creds = append(tx.Creds, cred) + } + + txBytes, err := Codec.Marshal(genericTx{Tx: &tx}) + if err != nil { + return errCreatingTransaction + } + + response.Tx.Bytes = txBytes + return nil +} + // IssueTxArgs are the arguments to IssueTx type IssueTxArgs struct { // Tx being sent to the network @@ -800,69 +1028,25 @@ func (service *Service) IssueTx(_ *http.Request, args *IssueTxArgs, response *Is return fmt.Errorf("error initializing tx: %s", err) } service.vm.unissuedEvents.Push(tx) - defer service.vm.resetTimer() response.TxID = tx.ID() - return nil - case *CreateSubnetTx: + case DecisionTx: if err := tx.initialize(service.vm); err != nil { return fmt.Errorf("error initializing tx: %s", err) } service.vm.unissuedDecisionTxs = append(service.vm.unissuedDecisionTxs, tx) - defer service.vm.resetTimer() - response.TxID = tx.ID - return nil + response.TxID = tx.ID() + case AtomicTx: + if err := tx.initialize(service.vm); err != nil { + return fmt.Errorf("error initializing tx: %s", err) + } + service.vm.unissuedAtomicTxs = append(service.vm.unissuedAtomicTxs, tx) + response.TxID = tx.ID() default: - return errors.New("Could not parse given tx. Must be one of: addDefaultSubnetValidatorTx, addDefaultSubnetDelegatorTx, addNonDefaultSubnetValidatorTx, createSubnetTx") - } -} - -/* - ****************************************************** - **************** Create a Subnet ********************* - ****************************************************** - */ - -// CreateSubnetArgs are the arguments to CreateSubnet -type CreateSubnetArgs struct { - // The ID member of APISubnet is ignored - APISubnet - - // Nonce of the account that pays the transaction fee - PayerNonce json.Uint64 `json:"payerNonce"` -} - -// CreateSubnetResponse is the response from a call to CreateSubnet -type CreateSubnetResponse struct { - // Byte representation of the unsigned transaction to create a new subnet - UnsignedTx formatting.CB58 `json:"unsignedTx"` -} - -// CreateSubnet returns an unsigned transaction to create a new subnet. -// The unsigned transaction must be signed with the key of [args.Payer] -func (service *Service) CreateSubnet(_ *http.Request, args *CreateSubnetArgs, response *CreateSubnetResponse) error { - service.vm.Ctx.Log.Debug("platform.createSubnet called") - - // Create the transaction - tx := CreateSubnetTx{ - UnsignedCreateSubnetTx: UnsignedCreateSubnetTx{ - NetworkID: service.vm.Ctx.NetworkID, - Nonce: uint64(args.PayerNonce), - ControlKeys: args.ControlKeys, - Threshold: uint16(args.Threshold), - }, - key: nil, - Sig: [65]byte{}, - bytes: nil, + return errors.New("Could not parse given tx. Must be a TimedTx, DecisionTx, or AtomicTx") } - txBytes, err := Codec.Marshal(genericTx{Tx: &tx}) - if err != nil { - return errCreatingTransaction - } - - response.UnsignedTx.Bytes = txBytes + service.vm.resetTimer() return nil - } /* @@ -1028,3 +1212,176 @@ func (service *Service) chainExists(blockID ids.ID, chainID ids.ID) (bool, error return false, 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 +// } diff --git a/vms/platformvm/standard_block.go b/vms/platformvm/standard_block.go index f3c89c9..5f7e300 100644 --- a/vms/platformvm/standard_block.go +++ b/vms/platformvm/standard_block.go @@ -12,6 +12,8 @@ import ( // DecisionTx is an operation that can be decided without being proposed type DecisionTx interface { + ID() ids.ID + initialize(vm *VM) error // Attempt to verify this transaction with the provided state. The provided diff --git a/vms/platformvm/state.go b/vms/platformvm/state.go index 9febf25..7bc45da 100644 --- a/vms/platformvm/state.go +++ b/vms/platformvm/state.go @@ -211,18 +211,18 @@ func (vm *VM) getSubnets(db database.Database) ([]*CreateSubnetTx, error) { } // get the subnet with the specified ID -func (vm *VM) getSubnet(db database.Database, ID ids.ID) (*CreateSubnetTx, error) { +func (vm *VM) getSubnet(db database.Database, id ids.ID) (*CreateSubnetTx, error) { subnets, err := vm.getSubnets(db) if err != nil { return nil, err } for _, subnet := range subnets { - if subnet.ID.Equals(ID) { + if subnet.id.Equals(id) { return subnet, nil } } - return nil, fmt.Errorf("couldn't find subnet with ID %s", ID) + return nil, fmt.Errorf("couldn't find subnet with ID %s", id) } // register each type that we'll be storing in the database diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index ed2dfdc..0db5136 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -24,6 +24,7 @@ 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" @@ -632,7 +633,7 @@ func (vm *VM) nextValidatorChangeTime(db database.Database, start bool) time.Tim return earliest } for _, subnet := range subnets { - t := vm.nextSubnetValidatorChangeTime(db, subnet.ID, start) + t := vm.nextSubnetValidatorChangeTime(db, subnet.id, start) if t.Before(earliest) { earliest = t } @@ -740,3 +741,28 @@ 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 +} diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index d19b06c..00e436d 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -153,7 +153,7 @@ func defaultVM() *VM { &EventHeap{ SortByStartTime: false, }, - tx.ID, + tx.id, ) if err != nil { panic(err) @@ -163,7 +163,7 @@ func defaultVM() *VM { &EventHeap{ SortByStartTime: true, }, - tx.ID, + tx.id, ) if err != nil { panic(err) @@ -437,7 +437,7 @@ func TestAddNonDefaultSubnetValidatorAccept(t *testing.T) { uint64(startTime.Unix()), uint64(endTime.Unix()), keys[0].PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, keys[0], @@ -482,7 +482,7 @@ func TestAddNonDefaultSubnetValidatorAccept(t *testing.T) { commit.Accept() // accept the proposal // Verify that new validator is in pending validator set - pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.ID) + pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.id) if err != nil { t.Fatal(err) } @@ -510,7 +510,7 @@ func TestAddNonDefaultSubnetValidatorReject(t *testing.T) { uint64(startTime.Unix()), uint64(endTime.Unix()), keys[0].PublicKey().Address(), - testSubnet1.ID, + testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[1], testSubnet1ControlKeys[2]}, keys[0], @@ -555,7 +555,7 @@ func TestAddNonDefaultSubnetValidatorReject(t *testing.T) { abort.Accept() // reject the proposal // Verify that new validator NOT in pending validator set - pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.ID) + pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.id) if err != nil { t.Fatal(err) } @@ -885,7 +885,7 @@ func TestCreateSubnet(t *testing.T) { uint64(startTime.Unix()), uint64(endTime.Unix()), keys[0].PublicKey().Address(), - createSubnetTx.ID, + createSubnetTx.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{keys[0]}, keys[0], @@ -935,7 +935,7 @@ func TestCreateSubnet(t *testing.T) { commit.Accept() // add the validator to pending validator set // Verify validator is in pending validator set - pendingValidators, err := vm.getPendingValidators(vm.DB, createSubnetTx.ID) + pendingValidators, err := vm.getPendingValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } @@ -989,7 +989,7 @@ func TestCreateSubnet(t *testing.T) { // Verify validator no longer in pending validator set // Verify validator is in pending validator set - pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.ID) + pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } @@ -998,7 +998,7 @@ func TestCreateSubnet(t *testing.T) { } // Verify validator is in current validator set - currentValidators, err := vm.getCurrentValidators(vm.DB, createSubnetTx.ID) + currentValidators, err := vm.getCurrentValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } @@ -1048,14 +1048,14 @@ func TestCreateSubnet(t *testing.T) { commit.Accept() // remove validator from current validator set // pending validators and current validator should be empty - pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.ID) + pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } if pendingValidators.Len() != 0 { t.Fatal("pending validator set should be empty") } - currentValidators, err = vm.getCurrentValidators(vm.DB, createSubnetTx.ID) + currentValidators, err = vm.getCurrentValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } From 6d9683dc99311c69d8cd17c9095131f5184eb06f Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Fri, 27 Mar 2020 03:39:35 -0400 Subject: [PATCH 25/25] Fixed minor atomic commit bugs --- vms/avm/unique_tx.go | 3 + vms/platformvm/service.go | 175 +------------------------------------- 2 files changed, 4 insertions(+), 174 deletions(-) diff --git a/vms/avm/unique_tx.go b/vms/avm/unique_tx.go index 6c4e3f4..354bb6c 100644 --- a/vms/avm/unique_tx.go +++ b/vms/avm/unique_tx.go @@ -191,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) diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index a2c8240..c04aa23 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -973,7 +973,7 @@ func (service *Service) CreateImportTx(_ *http.Request, args *CreateImportTxArgs } hash := hashing.ComputeHash256(unsignedTxBytes) - sig, err := key.SignHash(unsignedTxBytes) + sig, err := key.SignHash(hash) if err != nil { return errors.New("error while signing") } @@ -1212,176 +1212,3 @@ func (service *Service) chainExists(blockID ids.ID, chainID ids.ID) (bool, error return false, 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 -// }