Cleanup + introduction of side effects

This commit is contained in:
StephenButtolph 2020-03-20 20:56:43 -04:00
parent 638cce84ce
commit aee9d03921
7 changed files with 84 additions and 122 deletions

View File

@ -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 }

View File

@ -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) }

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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 {

View File

@ -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]