From d120bb4ed733fae65bbe516e48ef173a50d377f3 Mon Sep 17 00:00:00 2001 From: Diego Essaya Date: Wed, 26 Jul 2023 15:29:43 -0300 Subject: [PATCH 1/2] fix: properly store asset UTXOs --- packages/vm/core/accounts/foundries.go | 28 +++++++++--- packages/vm/core/accounts/foundryoutputrec.go | 9 ++-- packages/vm/core/accounts/impl.go | 4 +- packages/vm/core/accounts/impl_views.go | 2 +- packages/vm/core/accounts/internal.go | 14 ++++++ packages/vm/core/accounts/internal_test.go | 7 ++- .../vm/core/accounts/nativetokenoutputrec.go | 14 +++--- .../vm/core/accounts/nativetokenoutputs.go | 28 +++++++++--- packages/vm/core/accounts/nftoutputrec.go | 15 +++---- packages/vm/core/accounts/nftoutputs.go | 30 ++++++++++--- packages/vm/core/testcore/blocklog_test.go | 21 +++++++++ packages/vm/vmimpl/privileged.go | 2 +- packages/vm/vmimpl/runtask.go | 8 ++++ packages/vm/vmimpl/txbuilder.go | 45 ++++--------------- 14 files changed, 144 insertions(+), 83 deletions(-) diff --git a/packages/vm/core/accounts/foundries.go b/packages/vm/core/accounts/foundries.go index 3d0da4eb17..bb25971d3d 100644 --- a/packages/vm/core/accounts/foundries.go +++ b/packages/vm/core/accounts/foundries.go @@ -8,6 +8,10 @@ import ( "github.com/iotaledger/wasp/packages/kv/collections" ) +func newFoundriesArray(state kv.KVStore) *collections.Array { + return collections.NewArray(state, keyNewFoundries) +} + func accountFoundriesMap(state kv.KVStore, agentID isc.AgentID) *collections.Map { return collections.NewMap(state, foundriesMapKey(agentID)) } @@ -27,13 +31,27 @@ func allFoundriesMapR(state kv.KVStoreReader) *collections.ImmutableMap { // SaveFoundryOutput stores foundry output into the map of all foundry outputs (compressed form) func SaveFoundryOutput(state kv.KVStore, f *iotago.FoundryOutput, blockIndex uint32, outputIndex uint16) { foundryRec := foundryOutputRec{ + // TransactionID is unknown yet, will be filled next block + OutputID: iotago.OutputIDFromTransactionIDAndIndex(iotago.TransactionID{}, outputIndex), Amount: f.Amount, TokenScheme: f.TokenScheme, Metadata: []byte{}, - BlockIndex: blockIndex, - OutputIndex: outputIndex, } allFoundriesMap(state).SetAt(codec.EncodeUint32(f.SerialNumber), foundryRec.Bytes()) + newFoundriesArray(state).Push(codec.EncodeUint32(f.SerialNumber)) +} + +func updateFoundryOutputIDs(state kv.KVStore, anchorTxID iotago.TransactionID) { + newFoundries := newFoundriesArray(state) + allFoundries := allFoundriesMap(state) + n := newFoundries.Len() + for i := uint32(0); i < n; i++ { + k := newFoundries.GetAt(i) + rec := mustFoundryOutputRecFromBytes(allFoundries.GetAt(k)) + rec.OutputID = iotago.OutputIDFromTransactionIDAndIndex(anchorTxID, rec.OutputID.Index()) + allFoundries.SetAt(k, rec.Bytes()) + } + newFoundries.Erase() } // DeleteFoundryOutput deletes foundry output from the map of all foundries @@ -42,10 +60,10 @@ func DeleteFoundryOutput(state kv.KVStore, sn uint32) { } // GetFoundryOutput returns foundry output, its block number and output index -func GetFoundryOutput(state kv.KVStoreReader, sn uint32, chainID isc.ChainID) (*iotago.FoundryOutput, uint32, uint16) { +func GetFoundryOutput(state kv.KVStoreReader, sn uint32, chainID isc.ChainID) (*iotago.FoundryOutput, iotago.OutputID) { data := allFoundriesMapR(state).GetAt(codec.EncodeUint32(sn)) if data == nil { - return nil, 0, 0 + return nil, iotago.OutputID{} } rec := mustFoundryOutputRecFromBytes(data) @@ -59,7 +77,7 @@ func GetFoundryOutput(state kv.KVStoreReader, sn uint32, chainID isc.ChainID) (* }, Features: nil, } - return ret, rec.BlockIndex, rec.OutputIndex + return ret, rec.OutputID } // hasFoundry checks if specific account owns the foundry diff --git a/packages/vm/core/accounts/foundryoutputrec.go b/packages/vm/core/accounts/foundryoutputrec.go index 8858ebe07e..bda836dd27 100644 --- a/packages/vm/core/accounts/foundryoutputrec.go +++ b/packages/vm/core/accounts/foundryoutputrec.go @@ -10,8 +10,7 @@ import ( // foundryOutputRec contains information to reconstruct output type foundryOutputRec struct { - BlockIndex uint32 - OutputIndex uint16 + OutputID iotago.OutputID Amount uint64 // always storage deposit TokenScheme iotago.TokenScheme Metadata []byte @@ -35,8 +34,7 @@ func mustFoundryOutputRecFromBytes(data []byte) *foundryOutputRec { func (rec *foundryOutputRec) Read(r io.Reader) error { rr := rwutil.NewReader(r) - rec.BlockIndex = rr.ReadUint32() - rec.OutputIndex = rr.ReadUint16() + rr.ReadN(rec.OutputID[:]) rec.Amount = rr.ReadUint64() tokenScheme := rr.ReadBytes() if rr.Err == nil { @@ -48,8 +46,7 @@ func (rec *foundryOutputRec) Read(r io.Reader) error { func (rec *foundryOutputRec) Write(w io.Writer) error { ww := rwutil.NewWriter(w) - ww.WriteUint32(rec.BlockIndex) - ww.WriteUint16(rec.OutputIndex) + ww.WriteN(rec.OutputID[:]) ww.WriteUint64(rec.Amount) if ww.Err == nil { tokenScheme := codec.EncodeTokenScheme(rec.TokenScheme) diff --git a/packages/vm/core/accounts/impl.go b/packages/vm/core/accounts/impl.go index 89a788ebb4..377634e946 100644 --- a/packages/vm/core/accounts/impl.go +++ b/packages/vm/core/accounts/impl.go @@ -247,7 +247,7 @@ func foundryDestroy(ctx isc.Sandbox) dict.Dict { panic(vm.ErrUnauthorized) } - out, _, _ := GetFoundryOutput(state, sn, ctx.ChainID()) + out, _ := GetFoundryOutput(state, sn, ctx.ChainID()) simpleTokenScheme := util.MustTokenScheme(out.TokenScheme) if !util.IsZeroBigInt(big.NewInt(0).Sub(simpleTokenScheme.MintedTokens, simpleTokenScheme.MeltedTokens)) { panic(errFoundryWithCirculatingSupply) @@ -286,7 +286,7 @@ func foundryModifySupply(ctx isc.Sandbox) dict.Dict { panic(vm.ErrUnauthorized) } - out, _, _ := GetFoundryOutput(state, sn, ctx.ChainID()) + out, _ := GetFoundryOutput(state, sn, ctx.ChainID()) nativeTokenID, err := out.NativeTokenID() ctx.RequireNoError(err, "internal") diff --git a/packages/vm/core/accounts/impl_views.go b/packages/vm/core/accounts/impl_views.go index 596307155b..fb194866bb 100644 --- a/packages/vm/core/accounts/impl_views.go +++ b/packages/vm/core/accounts/impl_views.go @@ -94,7 +94,7 @@ func viewFoundryOutput(ctx isc.SandboxView) dict.Dict { ctx.Log().Debugf("accounts.viewFoundryOutput") sn := ctx.Params().MustGetUint32(ParamFoundrySN) - out, _, _ := GetFoundryOutput(ctx.StateR(), sn, ctx.ChainID()) + out, _ := GetFoundryOutput(ctx.StateR(), sn, ctx.ChainID()) if out == nil { panic(errFoundryNotFound) } diff --git a/packages/vm/core/accounts/internal.go b/packages/vm/core/accounts/internal.go index 1600e433e1..6e1c296848 100644 --- a/packages/vm/core/accounts/internal.go +++ b/packages/vm/core/accounts/internal.go @@ -3,6 +3,7 @@ package accounts import ( "errors" + iotago "github.com/iotaledger/iota.go/v3" "github.com/iotaledger/wasp/packages/isc" "github.com/iotaledger/wasp/packages/kv" "github.com/iotaledger/wasp/packages/kv/codec" @@ -58,6 +59,13 @@ const ( keyNFTOutputRecords = "NO" // keyNFTData stores a map of => isc.NFT keyNFTData = "ND" + + // keyNewNativeTokens stores an array of , containing the newly created native tokens that need filling out the OutputID + keyNewNativeTokens = "TN" + // keyNewFoundries stores an array of , containing the newly created foundries that need filling out the OutputID + keyNewFoundries = "FN" + // keyNewNFTs stores an array of , containing the newly created NFTs that need filling out the OutputID + keyNewNFTs = "NN" ) func accountKey(agentID isc.AgentID) kv.Key { @@ -159,3 +167,9 @@ func debitBaseTokensFromAllowance(ctx isc.Sandbox, amount uint64) { ctx.TransferAllowedFunds(CommonAccount(), storageDepositAssets) DebitFromAccount(ctx.State(), CommonAccount(), storageDepositAssets) } + +func UpdateLatestOutputID(state kv.KVStore, anchorTxID iotago.TransactionID) { + updateNativeTokenOutputIDs(state, anchorTxID) + updateFoundryOutputIDs(state, anchorTxID) + updateNFTOutputIDs(state, anchorTxID) +} diff --git a/packages/vm/core/accounts/internal_test.go b/packages/vm/core/accounts/internal_test.go index ed8bf66892..9191423948 100644 --- a/packages/vm/core/accounts/internal_test.go +++ b/packages/vm/core/accounts/internal_test.go @@ -344,15 +344,14 @@ func TestTransferNFTs(t *testing.T) { func TestFoundryOutputRecSerialization(t *testing.T) { o := foundryOutputRec{ - Amount: 300, + OutputID: iotago.OutputID{1, 2, 3}, + Amount: 300, TokenScheme: &iotago.SimpleTokenScheme{ MaximumSupply: big.NewInt(1000), MintedTokens: big.NewInt(20), MeltedTokens: big.NewInt(1), }, - Metadata: []byte("Tralala"), - BlockIndex: 3, - OutputIndex: 2, + Metadata: []byte("Tralala"), } rwutil.ReadWriteTest(t, &o, new(foundryOutputRec)) rwutil.BytesTest(t, &o, foundryOutputRecFromBytes) diff --git a/packages/vm/core/accounts/nativetokenoutputrec.go b/packages/vm/core/accounts/nativetokenoutputrec.go index 0547ede4ab..3bab055b46 100644 --- a/packages/vm/core/accounts/nativetokenoutputrec.go +++ b/packages/vm/core/accounts/nativetokenoutputrec.go @@ -5,12 +5,12 @@ import ( "io" "math/big" + iotago "github.com/iotaledger/iota.go/v3" "github.com/iotaledger/wasp/packages/util/rwutil" ) type nativeTokenOutputRec struct { - BlockIndex uint32 - OutputIndex uint16 + OutputID iotago.OutputID Amount *big.Int StorageBaseTokens uint64 // always storage deposit } @@ -32,14 +32,13 @@ func (rec *nativeTokenOutputRec) Bytes() []byte { } func (rec *nativeTokenOutputRec) String() string { - return fmt.Sprintf("Native Token Account: base tokens: %d, amount: %d, block: %d, outIdx: %d", - rec.StorageBaseTokens, rec.Amount, rec.BlockIndex, rec.OutputIndex) + return fmt.Sprintf("Native Token Account: base tokens: %d, amount: %d, outID: %s", + rec.StorageBaseTokens, rec.Amount, rec.OutputID) } func (rec *nativeTokenOutputRec) Read(r io.Reader) error { rr := rwutil.NewReader(r) - rec.BlockIndex = rr.ReadUint32() - rec.OutputIndex = rr.ReadUint16() + rr.ReadN(rec.OutputID[:]) rec.Amount = rr.ReadUint256() rec.StorageBaseTokens = rr.ReadAmount64() return rr.Err @@ -47,8 +46,7 @@ func (rec *nativeTokenOutputRec) Read(r io.Reader) error { func (rec *nativeTokenOutputRec) Write(w io.Writer) error { ww := rwutil.NewWriter(w) - ww.WriteUint32(rec.BlockIndex) - ww.WriteUint16(rec.OutputIndex) + ww.WriteN(rec.OutputID[:]) ww.WriteUint256(rec.Amount) ww.WriteAmount64(rec.StorageBaseTokens) return ww.Err diff --git a/packages/vm/core/accounts/nativetokenoutputs.go b/packages/vm/core/accounts/nativetokenoutputs.go index 9b432d2553..223c6fc3f8 100644 --- a/packages/vm/core/accounts/nativetokenoutputs.go +++ b/packages/vm/core/accounts/nativetokenoutputs.go @@ -8,6 +8,10 @@ import ( "github.com/iotaledger/wasp/packages/kv/collections" ) +func newNativeTokensArray(state kv.KVStore) *collections.Array { + return collections.NewArray(state, keyNewNativeTokens) +} + func nativeTokenOutputMap(state kv.KVStore) *collections.Map { return collections.NewMap(state, keyNativeTokenOutputMap) } @@ -19,22 +23,36 @@ func nativeTokenOutputMapR(state kv.KVStoreReader) *collections.ImmutableMap { // SaveNativeTokenOutput map nativeTokenID -> foundryRec func SaveNativeTokenOutput(state kv.KVStore, out *iotago.BasicOutput, blockIndex uint32, outputIndex uint16) { tokenRec := nativeTokenOutputRec{ + // TransactionID is unknown yet, will be filled next block + OutputID: iotago.OutputIDFromTransactionIDAndIndex(iotago.TransactionID{}, outputIndex), StorageBaseTokens: out.Amount, Amount: out.NativeTokens[0].Amount, - BlockIndex: blockIndex, - OutputIndex: outputIndex, } nativeTokenOutputMap(state).SetAt(out.NativeTokens[0].ID[:], tokenRec.Bytes()) + newNativeTokensArray(state).Push(out.NativeTokens[0].ID[:]) +} + +func updateNativeTokenOutputIDs(state kv.KVStore, anchorTxID iotago.TransactionID) { + newNativeTokens := newNativeTokensArray(state) + allNativeTokens := nativeTokenOutputMap(state) + n := newNativeTokens.Len() + for i := uint32(0); i < n; i++ { + k := newNativeTokens.GetAt(i) + rec := mustNativeTokenOutputRecFromBytes(allNativeTokens.GetAt(k)) + rec.OutputID = iotago.OutputIDFromTransactionIDAndIndex(anchorTxID, rec.OutputID.Index()) + allNativeTokens.SetAt(k, rec.Bytes()) + } + newNativeTokens.Erase() } func DeleteNativeTokenOutput(state kv.KVStore, nativeTokenID iotago.NativeTokenID) { nativeTokenOutputMap(state).DelAt(nativeTokenID[:]) } -func GetNativeTokenOutput(state kv.KVStoreReader, nativeTokenID iotago.NativeTokenID, chainID isc.ChainID) (*iotago.BasicOutput, uint32, uint16) { +func GetNativeTokenOutput(state kv.KVStoreReader, nativeTokenID iotago.NativeTokenID, chainID isc.ChainID) (*iotago.BasicOutput, iotago.OutputID) { data := nativeTokenOutputMapR(state).GetAt(nativeTokenID[:]) if data == nil { - return nil, 0, 0 + return nil, iotago.OutputID{} } tokenRec := mustNativeTokenOutputRecFromBytes(data) ret := &iotago.BasicOutput{ @@ -52,5 +70,5 @@ func GetNativeTokenOutput(state kv.KVStoreReader, nativeTokenID iotago.NativeTok }, }, } - return ret, tokenRec.BlockIndex, tokenRec.OutputIndex + return ret, tokenRec.OutputID } diff --git a/packages/vm/core/accounts/nftoutputrec.go b/packages/vm/core/accounts/nftoutputrec.go index 9c5b47e56c..776e75c7a1 100644 --- a/packages/vm/core/accounts/nftoutputrec.go +++ b/packages/vm/core/accounts/nftoutputrec.go @@ -9,9 +9,8 @@ import ( ) type NFTOutputRec struct { - BlockIndex uint32 - OutputIndex uint16 - Output *iotago.NFTOutput + OutputID iotago.OutputID + Output *iotago.NFTOutput } func nftOutputRecFromBytes(data []byte) (*NFTOutputRec, error) { @@ -31,14 +30,13 @@ func (rec *NFTOutputRec) Bytes() []byte { } func (rec *NFTOutputRec) String() string { - return fmt.Sprintf("NFT Record: base tokens: %d, ID: %s, block: %d, outIdx: %d", - rec.Output.Deposit(), rec.Output.NFTID, rec.BlockIndex, rec.OutputIndex) + return fmt.Sprintf("NFT Record: base tokens: %d, ID: %s, outID: %s", + rec.Output.Deposit(), rec.Output.NFTID, rec.OutputID) } func (rec *NFTOutputRec) Read(r io.Reader) error { rr := rwutil.NewReader(r) - rec.BlockIndex = rr.ReadUint32() - rec.OutputIndex = rr.ReadUint16() + rr.ReadN(rec.OutputID[:]) rec.Output = new(iotago.NFTOutput) rr.ReadSerialized(rec.Output) return rr.Err @@ -46,8 +44,7 @@ func (rec *NFTOutputRec) Read(r io.Reader) error { func (rec *NFTOutputRec) Write(w io.Writer) error { ww := rwutil.NewWriter(w) - ww.WriteUint32(rec.BlockIndex) - ww.WriteUint16(rec.OutputIndex) + ww.WriteN(rec.OutputID[:]) ww.WriteSerialized(rec.Output) return ww.Err } diff --git a/packages/vm/core/accounts/nftoutputs.go b/packages/vm/core/accounts/nftoutputs.go index 6dc62248c7..f3aa33c028 100644 --- a/packages/vm/core/accounts/nftoutputs.go +++ b/packages/vm/core/accounts/nftoutputs.go @@ -6,6 +6,10 @@ import ( "github.com/iotaledger/wasp/packages/kv/collections" ) +func newNFTsArray(state kv.KVStore) *collections.Array { + return collections.NewArray(state, keyNewNFTs) +} + func nftOutputMap(state kv.KVStore) *collections.Map { return collections.NewMap(state, keyNFTOutputRecords) } @@ -17,22 +21,36 @@ func nftOutputMapR(state kv.KVStoreReader) *collections.ImmutableMap { // SaveNFTOutput map tokenID -> foundryRec func SaveNFTOutput(state kv.KVStore, out *iotago.NFTOutput, blockIndex uint32, outputIndex uint16) { tokenRec := NFTOutputRec{ - Output: out, - BlockIndex: blockIndex, - OutputIndex: outputIndex, + // TransactionID is unknown yet, will be filled next block + OutputID: iotago.OutputIDFromTransactionIDAndIndex(iotago.TransactionID{}, outputIndex), + Output: out, } nftOutputMap(state).SetAt(out.NFTID[:], tokenRec.Bytes()) + newNFTsArray(state).Push(out.NFTID[:]) +} + +func updateNFTOutputIDs(state kv.KVStore, anchorTxID iotago.TransactionID) { + newNFTs := newNFTsArray(state) + allNFTs := nftOutputMap(state) + n := newNFTs.Len() + for i := uint32(0); i < n; i++ { + k := newNFTs.GetAt(i) + rec := mustNFTOutputRecFromBytes(allNFTs.GetAt(k)) + rec.OutputID = iotago.OutputIDFromTransactionIDAndIndex(anchorTxID, rec.OutputID.Index()) + allNFTs.SetAt(k, rec.Bytes()) + } + newNFTs.Erase() } func DeleteNFTOutput(state kv.KVStore, id iotago.NFTID) { nftOutputMap(state).DelAt(id[:]) } -func GetNFTOutput(state kv.KVStoreReader, id iotago.NFTID) (*iotago.NFTOutput, uint32, uint16) { +func GetNFTOutput(state kv.KVStoreReader, id iotago.NFTID) (*iotago.NFTOutput, iotago.OutputID) { data := nftOutputMapR(state).GetAt(id[:]) if data == nil { - return nil, 0, 0 + return nil, iotago.OutputID{} } tokenRec := mustNFTOutputRecFromBytes(data) - return tokenRec.Output, tokenRec.BlockIndex, tokenRec.OutputIndex + return tokenRec.Output, tokenRec.OutputID } diff --git a/packages/vm/core/testcore/blocklog_test.go b/packages/vm/core/testcore/blocklog_test.go index 62d1478ff4..f262a3f230 100644 --- a/packages/vm/core/testcore/blocklog_test.go +++ b/packages/vm/core/testcore/blocklog_test.go @@ -241,3 +241,24 @@ func TestBlocklogPruning(t *testing.T) { require.EqualValues(t, i, evmBlock.Number().Uint64()) } } + +func TestBlocklogFoundriesWithPruning(t *testing.T) { + // test that foundries can be accessed even after the block is pruned + + env := solo.New(t, &solo.InitOptions{AutoAdjustStorageDeposit: true, Debug: true}) + ch, _ := env.NewChainExt(nil, 10*isc.Million, "chain1", dict.Dict{ + origin.ParamBlockKeepAmount: codec.EncodeInt32(10), + }) + ch.DepositBaseTokensToL2(1*isc.Million, nil) + + sn, _, err := ch.NewFoundryParams(10).CreateFoundry() + require.NoError(t, err) + + // provoke the block where the foundry was stored to be pruned + for i := 1; i <= 20; i++ { + ch.DepositBaseTokensToL2(1000, nil) + } + + err = ch.DestroyFoundry(sn, ch.OriginatorPrivateKey) + require.NoError(t, err) +} diff --git a/packages/vm/vmimpl/privileged.go b/packages/vm/vmimpl/privileged.go index 96a4214aed..10f9d73f7a 100644 --- a/packages/vm/vmimpl/privileged.go +++ b/packages/vm/vmimpl/privileged.go @@ -42,7 +42,7 @@ func (reqctx *requestContext) DestroyFoundry(sn uint32) uint64 { func (reqctx *requestContext) ModifyFoundrySupply(sn uint32, delta *big.Int) int64 { reqctx.mustBeCalledFromContract(accounts.Contract) - out, _, _ := accounts.GetFoundryOutput(reqctx.contractStateWithGasBurn(), sn, reqctx.ChainID()) + out, _ := accounts.GetFoundryOutput(reqctx.contractStateWithGasBurn(), sn, reqctx.ChainID()) nativeTokenID, err := out.NativeTokenID() if err != nil { panic(fmt.Errorf("internal: %w", err)) diff --git a/packages/vm/vmimpl/runtask.go b/packages/vm/vmimpl/runtask.go index 4afb403862..7428b872ed 100644 --- a/packages/vm/vmimpl/runtask.go +++ b/packages/vm/vmimpl/runtask.go @@ -13,6 +13,7 @@ import ( "github.com/iotaledger/wasp/packages/transaction" "github.com/iotaledger/wasp/packages/util/panicutil" "github.com/iotaledger/wasp/packages/vm" + "github.com/iotaledger/wasp/packages/vm/core/accounts" "github.com/iotaledger/wasp/packages/vm/core/blocklog" "github.com/iotaledger/wasp/packages/vm/core/governance" "github.com/iotaledger/wasp/packages/vm/core/migrations" @@ -119,6 +120,13 @@ func (vmctx *vmContext) init(prevL1Commitment *state.L1Commitment) { }) }) + // save the OutputID of the newly created tokens, foundries and NFTs in the previous block + vmctx.withStateUpdate(func(chainState kv.KVStore) { + withContractState(chainState, accounts.Contract, func(s kv.KVStore) { + accounts.UpdateLatestOutputID(s, vmctx.task.AnchorOutputID.TransactionID()) + }) + }) + vmctx.txbuilder = vmtxbuilder.NewAnchorTransactionBuilder( vmctx.task.AnchorOutput, vmctx.task.AnchorOutputID, diff --git a/packages/vm/vmimpl/txbuilder.go b/packages/vm/vmimpl/txbuilder.go index fcab175310..403404bb9e 100644 --- a/packages/vm/vmimpl/txbuilder.go +++ b/packages/vm/vmimpl/txbuilder.go @@ -51,36 +51,18 @@ func (vmctx *vmContext) restoreTxBuilderSnapshot(snapshot *vmtxbuilder.AnchorTra vmctx.txbuilder = snapshot } -func (vmctx *vmContext) loadNativeTokenOutput(nativeTokenID iotago.NativeTokenID) (*iotago.BasicOutput, iotago.OutputID) { - var retOut *iotago.BasicOutput - var blockIndex uint32 - var outputIndex uint16 +func (vmctx *vmContext) loadNativeTokenOutput(nativeTokenID iotago.NativeTokenID) (out *iotago.BasicOutput, id iotago.OutputID) { withContractState(vmctx.stateDraft, accounts.Contract, func(s kv.KVStore) { - retOut, blockIndex, outputIndex = accounts.GetNativeTokenOutput(s, nativeTokenID, vmctx.ChainID()) + out, id = accounts.GetNativeTokenOutput(s, nativeTokenID, vmctx.ChainID()) }) - if retOut == nil { - return nil, iotago.OutputID{} - } - - outputID := vmctx.getOutputID(blockIndex, outputIndex) - - return retOut, outputID + return } -func (vmctx *vmContext) loadFoundry(serNum uint32) (*iotago.FoundryOutput, iotago.OutputID) { - var foundryOutput *iotago.FoundryOutput - var blockIndex uint32 - var outputIndex uint16 +func (vmctx *vmContext) loadFoundry(serNum uint32) (out *iotago.FoundryOutput, id iotago.OutputID) { withContractState(vmctx.stateDraft, accounts.Contract, func(s kv.KVStore) { - foundryOutput, blockIndex, outputIndex = accounts.GetFoundryOutput(s, serNum, vmctx.ChainID()) + out, id = accounts.GetFoundryOutput(s, serNum, vmctx.ChainID()) }) - if foundryOutput == nil { - return nil, iotago.OutputID{} - } - - outputID := vmctx.getOutputID(blockIndex, outputIndex) - - return foundryOutput, outputID + return } func (vmctx *vmContext) getOutputID(blockIndex uint32, outputIndex uint16) iotago.OutputID { @@ -98,20 +80,11 @@ func (vmctx *vmContext) getOutputID(blockIndex uint32, outputIndex uint16) iotag return outputID } -func (vmctx *vmContext) loadNFT(id iotago.NFTID) (*iotago.NFTOutput, iotago.OutputID) { - var nftOutput *iotago.NFTOutput - var blockIndex uint32 - var outputIndex uint16 +func (vmctx *vmContext) loadNFT(nftID iotago.NFTID) (out *iotago.NFTOutput, id iotago.OutputID) { withContractState(vmctx.stateDraft, accounts.Contract, func(s kv.KVStore) { - nftOutput, blockIndex, outputIndex = accounts.GetNFTOutput(s, id) + out, id = accounts.GetNFTOutput(s, nftID) }) - if nftOutput == nil { - return nil, iotago.OutputID{} - } - - outputID := vmctx.getOutputID(blockIndex, outputIndex) - - return nftOutput, outputID + return } func (vmctx *vmContext) loadTotalFungibleTokens() *isc.Assets { From 1676c9a3e9cebe0cffe92526b1791efb38f16e7a Mon Sep 17 00:00:00 2001 From: Diego Essaya Date: Thu, 27 Jul 2023 11:04:00 -0300 Subject: [PATCH 2/2] migration: delete assets on testnet --- packages/apilib/deploychain.go | 2 + packages/chain/cons/bp/bp_test.go | 2 + packages/chain/cons/cons_test.go | 2 + packages/kv/collections/map.go | 6 ++- packages/origin/origin.go | 10 ++-- packages/origin/origin_test.go | 12 ++--- packages/solo/run.go | 1 + packages/solo/solo.go | 8 +++ packages/solo/solotest/solo_test.go | 2 - packages/testutil/dummystatemetadata.go | 3 +- .../testutil/testchain/test_chain_ledger.go | 2 + .../testdbhash/TestStorageContract.hex | 2 +- packages/vm/core/accounts/foundries.go | 8 +-- packages/vm/core/accounts/internal.go | 14 +++--- .../vm/core/accounts/nativetokenoutputs.go | 8 +-- packages/vm/core/accounts/nativetokens.go | 2 +- packages/vm/core/accounts/nftoutputs.go | 8 +-- packages/vm/core/accounts/nfts.go | 12 ++--- .../vm/core/migrations/allmigrations/all.go | 21 ++++++++ .../migrations/m001/reset_account_assets.go | 37 ++++++++++++++ .../m001/reset_account_assets_test.go | 49 +++++++++++++++++++ packages/vm/core/migrations/migration.go | 21 ++++++++ packages/vm/core/migrations/migrations.go | 23 --------- packages/vm/core/testcore/accounts_test.go | 3 +- packages/vm/vmimpl/migrations.go | 10 ++-- packages/vm/vmimpl/migrations_test.go | 20 ++++++-- packages/vm/vmimpl/runtask.go | 11 ++++- packages/vm/vmtask.go | 3 ++ tools/cluster/tests/nodeconn_test.go | 2 + 29 files changed, 224 insertions(+), 80 deletions(-) create mode 100644 packages/vm/core/migrations/allmigrations/all.go create mode 100644 packages/vm/core/migrations/m001/reset_account_assets.go create mode 100644 packages/vm/core/migrations/m001/reset_account_assets_test.go create mode 100644 packages/vm/core/migrations/migration.go delete mode 100644 packages/vm/core/migrations/migrations.go diff --git a/packages/apilib/deploychain.go b/packages/apilib/deploychain.go index 6314fad26b..9d9dbf5bb5 100644 --- a/packages/apilib/deploychain.go +++ b/packages/apilib/deploychain.go @@ -16,6 +16,7 @@ import ( "github.com/iotaledger/wasp/packages/origin" "github.com/iotaledger/wasp/packages/parameters" "github.com/iotaledger/wasp/packages/registry" + "github.com/iotaledger/wasp/packages/vm/core/migrations/allmigrations" ) // TODO DeployChain on peering domain, not on committee @@ -99,6 +100,7 @@ func CreateChainOrigin( initParams, utxoMap, utxoIDsFromUtxoMap(utxoMap), + allmigrations.DefaultScheme.LatestSchemaVersion(), ) if err != nil { return isc.ChainID{}, fmt.Errorf("CreateChainOrigin: %w", err) diff --git a/packages/chain/cons/bp/bp_test.go b/packages/chain/cons/bp/bp_test.go index 240aec77d6..1653ef7914 100644 --- a/packages/chain/cons/bp/bp_test.go +++ b/packages/chain/cons/bp/bp_test.go @@ -18,6 +18,7 @@ import ( "github.com/iotaledger/wasp/packages/transaction" "github.com/iotaledger/wasp/packages/util" "github.com/iotaledger/wasp/packages/vm/core/governance" + "github.com/iotaledger/wasp/packages/vm/core/migrations/allmigrations" "github.com/iotaledger/wasp/packages/vm/gas" ) @@ -40,6 +41,7 @@ func TestOffLedgerOrdering(t *testing.T) { nil, outputs, outIDs, + allmigrations.DefaultScheme.LatestSchemaVersion(), ) require.NoError(t, err) stateAnchor, aliasOutput, err := transaction.GetAnchorFromTransaction(originTX) diff --git a/packages/chain/cons/cons_test.go b/packages/chain/cons/cons_test.go index 8f2d9d6177..22f64322bc 100644 --- a/packages/chain/cons/cons_test.go +++ b/packages/chain/cons/cons_test.go @@ -33,6 +33,7 @@ import ( "github.com/iotaledger/wasp/packages/transaction" "github.com/iotaledger/wasp/packages/vm/core/accounts" "github.com/iotaledger/wasp/packages/vm/core/coreprocessors" + "github.com/iotaledger/wasp/packages/vm/core/migrations/allmigrations" "github.com/iotaledger/wasp/packages/vm/processors" "github.com/iotaledger/wasp/packages/vm/vmimpl" ) @@ -91,6 +92,7 @@ func testConsBasic(t *testing.T, n, f int) { nil, outputs, outIDs, + allmigrations.DefaultScheme.LatestSchemaVersion(), ) require.NoError(t, err) stateAnchor, aliasOutput, err := transaction.GetAnchorFromTransaction(originTX) diff --git a/packages/kv/collections/map.go b/packages/kv/collections/map.go index b160cc017d..6faed01c31 100644 --- a/packages/kv/collections/map.go +++ b/packages/kv/collections/map.go @@ -101,10 +101,14 @@ func (m *ImmutableMap) Len() uint32 { // Erase the map. func (m *Map) Erase() { + var keys [][]byte m.IterateKeys(func(elemKey []byte) bool { - m.DelAt(elemKey) + keys = append(keys, elemKey) return true }) + for _, k := range keys { + m.DelAt(k) + } } // Iterate non-deterministic diff --git a/packages/origin/origin.go b/packages/origin/origin.go index 221b44b8c4..5a6cd2fcf1 100644 --- a/packages/origin/origin.go +++ b/packages/origin/origin.go @@ -25,7 +25,6 @@ import ( "github.com/iotaledger/wasp/packages/vm/core/evm/evmimpl" "github.com/iotaledger/wasp/packages/vm/core/governance" "github.com/iotaledger/wasp/packages/vm/core/governance/governanceimpl" - "github.com/iotaledger/wasp/packages/vm/core/migrations" "github.com/iotaledger/wasp/packages/vm/core/root" "github.com/iotaledger/wasp/packages/vm/core/root/rootimpl" "github.com/iotaledger/wasp/packages/vm/gas" @@ -114,11 +113,11 @@ func InitChainByAliasOutput(chainStore state.Store, aliasOutput *isc.AliasOutput return originBlock, nil } -func calcStateMetadata(initParams dict.Dict, commonAccountAmount uint64) []byte { +func calcStateMetadata(initParams dict.Dict, commonAccountAmount uint64, schemaVersion uint32) []byte { s := transaction.NewStateMetadata( L1Commitment(initParams, commonAccountAmount), gas.DefaultFeePolicy(), - migrations.BaseSchemaVersion+uint32(len(migrations.Migrations)), + schemaVersion, "", ) return s.Bytes() @@ -134,6 +133,7 @@ func NewChainOriginTransaction( initParams dict.Dict, unspentOutputs iotago.OutputSet, unspentOutputIDs iotago.OutputIDs, + schemaVersion uint32, ) (*iotago.Transaction, *iotago.AliasOutput, isc.ChainID, error) { if len(unspentOutputs) != len(unspentOutputIDs) { panic("mismatched lengths of outputs and inputs slices") @@ -151,7 +151,7 @@ func NewChainOriginTransaction( aliasOutput := &iotago.AliasOutput{ Amount: deposit, - StateMetadata: calcStateMetadata(initParams, deposit), // NOTE: Updated bellow. + StateMetadata: calcStateMetadata(initParams, deposit, schemaVersion), // NOTE: Updated below. Conditions: iotago.UnlockConditions{ &iotago.StateControllerAddressUnlockCondition{Address: stateControllerAddress}, &iotago.GovernorAddressUnlockCondition{Address: governanceControllerAddress}, @@ -167,7 +167,7 @@ func NewChainOriginTransaction( aliasOutput.Amount = minAmount } // update the L1 commitment to not include the minimumSD - aliasOutput.StateMetadata = calcStateMetadata(initParams, aliasOutput.Amount-minSD) + aliasOutput.StateMetadata = calcStateMetadata(initParams, aliasOutput.Amount-minSD, schemaVersion) txInputs, remainderOutput, err := transaction.ComputeInputsAndRemainder( walletAddr, diff --git a/packages/origin/origin_test.go b/packages/origin/origin_test.go index dc7c151279..31a3b776f2 100644 --- a/packages/origin/origin_test.go +++ b/packages/origin/origin_test.go @@ -1,7 +1,6 @@ package origin_test import ( - "bytes" "encoding/hex" "testing" @@ -20,7 +19,7 @@ import ( "github.com/iotaledger/wasp/packages/testutil/utxodb" "github.com/iotaledger/wasp/packages/transaction" "github.com/iotaledger/wasp/packages/vm/core/governance" - "github.com/iotaledger/wasp/packages/vm/core/migrations" + "github.com/iotaledger/wasp/packages/vm/core/migrations/allmigrations" "github.com/iotaledger/wasp/packages/vm/gas" ) @@ -67,6 +66,7 @@ func TestCreateOrigin(t *testing.T) { nil, allOutputs, ids, + allmigrations.DefaultScheme.LatestSchemaVersion(), ) require.NoError(t, err) @@ -103,15 +103,11 @@ func TestCreateOrigin(t *testing.T) { governance.DefaultMinBaseTokensOnCommonAccount, ), gas.DefaultFeePolicy(), - migrations.BaseSchemaVersion+uint32(len(migrations.Migrations)), + allmigrations.DefaultScheme.LatestSchemaVersion(), "", ) - require.True(t, - bytes.Equal( - originStateMetadata.Bytes(), - anchor.StateData), - ) + require.EqualValues(t, anchor.StateData, originStateMetadata.Bytes()) // only one output is expected in the ledger under the address of chainID outs, ids := u.GetUnspentOutputs(chainID.AsAddress()) diff --git a/packages/solo/run.go b/packages/solo/run.go index 5f899b6a57..0d76544738 100644 --- a/packages/solo/run.go +++ b/packages/solo/run.go @@ -72,6 +72,7 @@ func (ch *Chain) runTaskNoLock(reqs []isc.Request, estimateGas bool) *vm.VMTaskR // state baseline is always valid in Solo EnableGasBurnLogging: true, EstimateGasMode: estimateGas, + MigrationsOverride: ch.migrationScheme, } res, err := vmimpl.Run(task) diff --git a/packages/solo/solo.go b/packages/solo/solo.go index 2c1dcf5a9f..6574fe6c80 100644 --- a/packages/solo/solo.go +++ b/packages/solo/solo.go @@ -40,6 +40,7 @@ import ( "github.com/iotaledger/wasp/packages/util" "github.com/iotaledger/wasp/packages/vm/core/coreprocessors" "github.com/iotaledger/wasp/packages/vm/core/governance" + "github.com/iotaledger/wasp/packages/vm/core/migrations" "github.com/iotaledger/wasp/packages/vm/processors" _ "github.com/iotaledger/wasp/packages/vm/sandbox" "github.com/iotaledger/wasp/packages/vm/vmtypes" @@ -116,6 +117,8 @@ type Chain struct { RequestsBlock uint32 metrics *metrics.ChainMetrics + + migrationScheme *migrations.MigrationScheme } var _ chain.ChainCore = &Chain{} @@ -278,6 +281,7 @@ func (env *Solo) deployChain( initParams, outs, outIDs, + 0, ) require.NoError(env.T, err) @@ -470,6 +474,10 @@ func (ch *Chain) collateAndRunBatch() { } } +func (ch *Chain) AddMigration(m migrations.Migration) { + ch.migrationScheme.Migrations = append(ch.migrationScheme.Migrations, m) +} + func (ch *Chain) GetCandidateNodes() []*governance.AccessNodeInfo { panic("unimplemented") } diff --git a/packages/solo/solotest/solo_test.go b/packages/solo/solotest/solo_test.go index 1430fa22d3..d6f0ff2bf6 100644 --- a/packages/solo/solotest/solo_test.go +++ b/packages/solo/solotest/solo_test.go @@ -63,6 +63,4 @@ func TestLoadSnapshot(t *testing.T) { nativeTokenID, err := ch.GetNativeTokenIDByFoundrySN(1) require.NoError(t, err) ch.AssertL2NativeTokens(ch.OriginatorAgentID, nativeTokenID, 1000) - - require.NotEmpty(t, ch.L2NFTs(ch.OriginatorAgentID)) } diff --git a/packages/testutil/dummystatemetadata.go b/packages/testutil/dummystatemetadata.go index 911efd2eda..8c52619c0b 100644 --- a/packages/testutil/dummystatemetadata.go +++ b/packages/testutil/dummystatemetadata.go @@ -3,7 +3,6 @@ package testutil import ( "github.com/iotaledger/wasp/packages/state" "github.com/iotaledger/wasp/packages/transaction" - "github.com/iotaledger/wasp/packages/vm/core/migrations" "github.com/iotaledger/wasp/packages/vm/gas" ) @@ -11,7 +10,7 @@ func DummyStateMetadata(commitment *state.L1Commitment) *transaction.StateMetada return transaction.NewStateMetadata( commitment, gas.DefaultFeePolicy(), - migrations.BaseSchemaVersion+uint32(len(migrations.Migrations)), + 0, "", ) } diff --git a/packages/testutil/testchain/test_chain_ledger.go b/packages/testutil/testchain/test_chain_ledger.go index 8b143d2ab2..983ff15d68 100644 --- a/packages/testutil/testchain/test_chain_ledger.go +++ b/packages/testutil/testchain/test_chain_ledger.go @@ -18,6 +18,7 @@ import ( "github.com/iotaledger/wasp/packages/testutil/utxodb" "github.com/iotaledger/wasp/packages/transaction" "github.com/iotaledger/wasp/packages/vm/core/accounts" + "github.com/iotaledger/wasp/packages/vm/core/migrations/allmigrations" "github.com/iotaledger/wasp/packages/vm/core/root" "github.com/iotaledger/wasp/packages/vm/gas" ) @@ -57,6 +58,7 @@ func (tcl *TestChainLedger) MakeTxChainOrigin(committeeAddress iotago.Address) ( nil, outs, outIDs, + allmigrations.DefaultScheme.LatestSchemaVersion(), ) require.NoError(tcl.t, err) stateAnchor, aliasOutput, err := transaction.GetAnchorFromTransaction(originTX) diff --git a/packages/testutil/testdbhash/TestStorageContract.hex b/packages/testutil/testdbhash/TestStorageContract.hex index c1804f8409..34f69b5231 100644 --- a/packages/testutil/testdbhash/TestStorageContract.hex +++ b/packages/testutil/testdbhash/TestStorageContract.hex @@ -1 +1 @@ -0x7e9abb191e14b1426edc8a1c2ed107a638b47cba392f2ec9cd34246e7d049115 +0x1b4cea9b2a3cb5d9c9e21b37e3c03468f5b07048c5fec76b4c7e7e5d6b89b3a3 diff --git a/packages/vm/core/accounts/foundries.go b/packages/vm/core/accounts/foundries.go index bb25971d3d..241b234bd7 100644 --- a/packages/vm/core/accounts/foundries.go +++ b/packages/vm/core/accounts/foundries.go @@ -20,7 +20,7 @@ func accountFoundriesMapR(state kv.KVStoreReader, agentID isc.AgentID) *collecti return collections.NewMapReadOnly(state, foundriesMapKey(agentID)) } -func allFoundriesMap(state kv.KVStore) *collections.Map { +func AllFoundriesMap(state kv.KVStore) *collections.Map { return collections.NewMap(state, keyFoundryOutputRecords) } @@ -37,13 +37,13 @@ func SaveFoundryOutput(state kv.KVStore, f *iotago.FoundryOutput, blockIndex uin TokenScheme: f.TokenScheme, Metadata: []byte{}, } - allFoundriesMap(state).SetAt(codec.EncodeUint32(f.SerialNumber), foundryRec.Bytes()) + AllFoundriesMap(state).SetAt(codec.EncodeUint32(f.SerialNumber), foundryRec.Bytes()) newFoundriesArray(state).Push(codec.EncodeUint32(f.SerialNumber)) } func updateFoundryOutputIDs(state kv.KVStore, anchorTxID iotago.TransactionID) { newFoundries := newFoundriesArray(state) - allFoundries := allFoundriesMap(state) + allFoundries := AllFoundriesMap(state) n := newFoundries.Len() for i := uint32(0); i < n; i++ { k := newFoundries.GetAt(i) @@ -56,7 +56,7 @@ func updateFoundryOutputIDs(state kv.KVStore, anchorTxID iotago.TransactionID) { // DeleteFoundryOutput deletes foundry output from the map of all foundries func DeleteFoundryOutput(state kv.KVStore, sn uint32) { - allFoundriesMap(state).DelAt(codec.EncodeUint32(sn)) + AllFoundriesMap(state).DelAt(codec.EncodeUint32(sn)) } // GetFoundryOutput returns foundry output, its block number and output index diff --git a/packages/vm/core/accounts/internal.go b/packages/vm/core/accounts/internal.go index 6e1c296848..a7ca6c258d 100644 --- a/packages/vm/core/accounts/internal.go +++ b/packages/vm/core/accounts/internal.go @@ -32,18 +32,18 @@ const ( // prefixBaseTokens | stores the amount of base tokens (big.Int) prefixBaseTokens = "b" // prefixBaseTokens | stores a map of => big.Int - prefixNativeTokens = "t" + PrefixNativeTokens = "t" // l2TotalsAccount is the special storing the total fungible tokens // controlled by the chain l2TotalsAccount = "*" - // prefixNFTs | stores a map of => true - prefixNFTs = "n" - // prefixNFTsByCollection | | stores a map of => true - prefixNFTsByCollection = "c" - // prefixFoundries + stores a map of (uint32) => true - prefixFoundries = "f" + // PrefixNFTs | stores a map of => true + PrefixNFTs = "n" + // PrefixNFTsByCollection | | stores a map of => true + PrefixNFTsByCollection = "c" + // PrefixFoundries + stores a map of (uint32) => true + PrefixFoundries = "f" // noCollection is the special used for storing NFTs that do not belong in a collection noCollection = "-" diff --git a/packages/vm/core/accounts/nativetokenoutputs.go b/packages/vm/core/accounts/nativetokenoutputs.go index 223c6fc3f8..36b93db728 100644 --- a/packages/vm/core/accounts/nativetokenoutputs.go +++ b/packages/vm/core/accounts/nativetokenoutputs.go @@ -12,7 +12,7 @@ func newNativeTokensArray(state kv.KVStore) *collections.Array { return collections.NewArray(state, keyNewNativeTokens) } -func nativeTokenOutputMap(state kv.KVStore) *collections.Map { +func NativeTokenOutputMap(state kv.KVStore) *collections.Map { return collections.NewMap(state, keyNativeTokenOutputMap) } @@ -28,13 +28,13 @@ func SaveNativeTokenOutput(state kv.KVStore, out *iotago.BasicOutput, blockIndex StorageBaseTokens: out.Amount, Amount: out.NativeTokens[0].Amount, } - nativeTokenOutputMap(state).SetAt(out.NativeTokens[0].ID[:], tokenRec.Bytes()) + NativeTokenOutputMap(state).SetAt(out.NativeTokens[0].ID[:], tokenRec.Bytes()) newNativeTokensArray(state).Push(out.NativeTokens[0].ID[:]) } func updateNativeTokenOutputIDs(state kv.KVStore, anchorTxID iotago.TransactionID) { newNativeTokens := newNativeTokensArray(state) - allNativeTokens := nativeTokenOutputMap(state) + allNativeTokens := NativeTokenOutputMap(state) n := newNativeTokens.Len() for i := uint32(0); i < n; i++ { k := newNativeTokens.GetAt(i) @@ -46,7 +46,7 @@ func updateNativeTokenOutputIDs(state kv.KVStore, anchorTxID iotago.TransactionI } func DeleteNativeTokenOutput(state kv.KVStore, nativeTokenID iotago.NativeTokenID) { - nativeTokenOutputMap(state).DelAt(nativeTokenID[:]) + NativeTokenOutputMap(state).DelAt(nativeTokenID[:]) } func GetNativeTokenOutput(state kv.KVStoreReader, nativeTokenID iotago.NativeTokenID, chainID isc.ChainID) (*iotago.BasicOutput, iotago.OutputID) { diff --git a/packages/vm/core/accounts/nativetokens.go b/packages/vm/core/accounts/nativetokens.go index 7e786ff56e..238935b8ff 100644 --- a/packages/vm/core/accounts/nativetokens.go +++ b/packages/vm/core/accounts/nativetokens.go @@ -11,7 +11,7 @@ import ( ) func nativeTokensMapKey(accountKey kv.Key) string { - return prefixNativeTokens + string(accountKey) + return PrefixNativeTokens + string(accountKey) } func nativeTokensMapR(state kv.KVStoreReader, accountKey kv.Key) *collections.ImmutableMap { diff --git a/packages/vm/core/accounts/nftoutputs.go b/packages/vm/core/accounts/nftoutputs.go index f3aa33c028..e4fec899f4 100644 --- a/packages/vm/core/accounts/nftoutputs.go +++ b/packages/vm/core/accounts/nftoutputs.go @@ -10,7 +10,7 @@ func newNFTsArray(state kv.KVStore) *collections.Array { return collections.NewArray(state, keyNewNFTs) } -func nftOutputMap(state kv.KVStore) *collections.Map { +func NFTOutputMap(state kv.KVStore) *collections.Map { return collections.NewMap(state, keyNFTOutputRecords) } @@ -25,13 +25,13 @@ func SaveNFTOutput(state kv.KVStore, out *iotago.NFTOutput, blockIndex uint32, o OutputID: iotago.OutputIDFromTransactionIDAndIndex(iotago.TransactionID{}, outputIndex), Output: out, } - nftOutputMap(state).SetAt(out.NFTID[:], tokenRec.Bytes()) + NFTOutputMap(state).SetAt(out.NFTID[:], tokenRec.Bytes()) newNFTsArray(state).Push(out.NFTID[:]) } func updateNFTOutputIDs(state kv.KVStore, anchorTxID iotago.TransactionID) { newNFTs := newNFTsArray(state) - allNFTs := nftOutputMap(state) + allNFTs := NFTOutputMap(state) n := newNFTs.Len() for i := uint32(0); i < n; i++ { k := newNFTs.GetAt(i) @@ -43,7 +43,7 @@ func updateNFTOutputIDs(state kv.KVStore, anchorTxID iotago.TransactionID) { } func DeleteNFTOutput(state kv.KVStore, id iotago.NFTID) { - nftOutputMap(state).DelAt(id[:]) + NFTOutputMap(state).DelAt(id[:]) } func GetNFTOutput(state kv.KVStoreReader, id iotago.NFTID) (*iotago.NFTOutput, iotago.OutputID) { diff --git a/packages/vm/core/accounts/nfts.go b/packages/vm/core/accounts/nfts.go index b95cca8aac..17f2bb84a2 100644 --- a/packages/vm/core/accounts/nfts.go +++ b/packages/vm/core/accounts/nfts.go @@ -12,15 +12,15 @@ import ( ) func nftsMapKey(agentID isc.AgentID) string { - return prefixNFTs + string(agentID.Bytes()) + return PrefixNFTs + string(agentID.Bytes()) } func nftsByCollectionMapKey(agentID isc.AgentID, collectionKey kv.Key) string { - return prefixNFTsByCollection + string(agentID.Bytes()) + string(collectionKey) + return PrefixNFTsByCollection + string(agentID.Bytes()) + string(collectionKey) } func foundriesMapKey(agentID isc.AgentID) string { - return prefixFoundries + string(agentID.Bytes()) + return PrefixFoundries + string(agentID.Bytes()) } func nftsMapR(state kv.KVStoreReader, agentID isc.AgentID) *collections.ImmutableMap { @@ -31,7 +31,7 @@ func nftsMap(state kv.KVStore, agentID isc.AgentID) *collections.Map { return collections.NewMap(state, nftsMapKey(agentID)) } -func nftDataMap(state kv.KVStore) *collections.Map { +func NFTDataMap(state kv.KVStore) *collections.Map { return collections.NewMap(state, keyNFTData) } @@ -68,11 +68,11 @@ func saveNFTData(state kv.KVStore, nft *isc.NFT) { // note we store the NFT data without the leading id bytes ww.Skip().ReadN(nft.ID[:]) ww.Write(nft) - nftDataMap(state).SetAt(nft.ID[:], ww.Bytes()) + NFTDataMap(state).SetAt(nft.ID[:], ww.Bytes()) } func deleteNFTData(state kv.KVStore, id iotago.NFTID) { - allNFTs := nftDataMap(state) + allNFTs := NFTDataMap(state) if !allNFTs.HasAt(id[:]) { panic("deleteNFTData: inconsistency - NFT data doesn't exists") } diff --git a/packages/vm/core/migrations/allmigrations/all.go b/packages/vm/core/migrations/allmigrations/all.go new file mode 100644 index 0000000000..b492742579 --- /dev/null +++ b/packages/vm/core/migrations/allmigrations/all.go @@ -0,0 +1,21 @@ +package allmigrations + +import ( + "github.com/iotaledger/wasp/packages/vm/core/migrations" + "github.com/iotaledger/wasp/packages/vm/core/migrations/m001" +) + +var DefaultScheme = &migrations.MigrationScheme{ + BaseSchemaVersion: 0, + + // Add new migrations to the end of this list, and they will be applied before + // creating the next block. + // The first migration on the list is applied when schema version = + // BaseSchemaVersion, and after applying each migration the schema version is + // incremented. + // Old migrations can be pruned; for each migration pruned increment + // BaseSchemaVersion by one. + Migrations: []migrations.Migration{ + m001.ResetAccountAssets, + }, +} diff --git a/packages/vm/core/migrations/m001/reset_account_assets.go b/packages/vm/core/migrations/m001/reset_account_assets.go new file mode 100644 index 0000000000..3b17f46f5d --- /dev/null +++ b/packages/vm/core/migrations/m001/reset_account_assets.go @@ -0,0 +1,37 @@ +package m001 + +import ( + "github.com/iotaledger/hive.go/logger" + "github.com/iotaledger/wasp/packages/kv" + "github.com/iotaledger/wasp/packages/vm/core/accounts" + "github.com/iotaledger/wasp/packages/vm/core/migrations" +) + +// for testnet -- delete when deploying ShimmerEVM +var ResetAccountAssets = migrations.Migration{ + Contract: accounts.Contract, + Apply: func(accountsState kv.KVStore, log *logger.Logger) error { + accounts.NativeTokenOutputMap(accountsState).Erase() + erasePrefix(accountsState, accounts.PrefixNativeTokens) + + accounts.AllFoundriesMap(accountsState).Erase() + erasePrefix(accountsState, accounts.PrefixFoundries) + + erasePrefix(accountsState, accounts.PrefixNFTs) + erasePrefix(accountsState, accounts.PrefixNFTsByCollection) + accounts.NFTOutputMap(accountsState).Erase() + accounts.NFTDataMap(accountsState).Erase() + return nil + }, +} + +func erasePrefix(state kv.KVStore, prefix kv.Key) { + var keys []kv.Key + state.IterateKeys(prefix, func(k kv.Key) bool { + keys = append(keys, k) + return true + }) + for _, k := range keys { + state.Del(k) + } +} diff --git a/packages/vm/core/migrations/m001/reset_account_assets_test.go b/packages/vm/core/migrations/m001/reset_account_assets_test.go new file mode 100644 index 0000000000..7b4068fc18 --- /dev/null +++ b/packages/vm/core/migrations/m001/reset_account_assets_test.go @@ -0,0 +1,49 @@ +package m001_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/iotaledger/wasp/packages/solo" + "github.com/iotaledger/wasp/packages/vm/core/migrations/m001" +) + +func TestM001Migration(t *testing.T) { + // skipping, no need to store the snapshot and run this test after the migration is applied + t.SkipNow() + + env := solo.New(t, &solo.InitOptions{AutoAdjustStorageDeposit: true, Debug: true, PrintStackTrace: true}) + + // the snapshot is from commit 7ab50c4dfae7a2ee445eea88651e8ef6bea66592 + // created by running TestSaveSnapshot in packages/solo/solotest + env.LoadSnapshot("snapshot.db") + + ch := env.GetChainByName("chain1") + + require.EqualValues(t, 5, ch.LatestBlockIndex()) + + // add the migration to test + ch.AddMigration(m001.ResetAccountAssets) + + // cause a VM run, which will run the migration + err := ch.DepositBaseTokensToL2(1000, ch.OriginatorPrivateKey) + require.NoError(t, err) + + // in the snapshot the OriginatorAgentID owns a foundry and an NFT + assets := ch.L2Assets(ch.OriginatorAgentID) + require.Empty(t, assets.NFTs) + require.Empty(t, assets.NativeTokens) + + // create a foundry again, test that the migration is not run again + sn2, nativeTokenID2, err := ch.NewFoundryParams(1000).CreateFoundry() + require.NoError(t, err) + err = ch.MintTokens(sn2, 1000, ch.OriginatorPrivateKey) + require.NoError(t, err) + + // cause a VM run + err = ch.DepositBaseTokensToL2(1000, ch.OriginatorPrivateKey) + require.NoError(t, err) + + ch.AssertL2NativeTokens(ch.OriginatorAgentID, nativeTokenID2, 1000) +} diff --git a/packages/vm/core/migrations/migration.go b/packages/vm/core/migrations/migration.go new file mode 100644 index 0000000000..4ce7dd9513 --- /dev/null +++ b/packages/vm/core/migrations/migration.go @@ -0,0 +1,21 @@ +package migrations + +import ( + "github.com/iotaledger/hive.go/logger" + "github.com/iotaledger/wasp/packages/isc/coreutil" + "github.com/iotaledger/wasp/packages/kv" +) + +type Migration struct { + Contract *coreutil.ContractInfo + Apply func(contractState kv.KVStore, log *logger.Logger) error +} + +type MigrationScheme struct { + BaseSchemaVersion uint32 + Migrations []Migration +} + +func (m *MigrationScheme) LatestSchemaVersion() uint32 { + return m.BaseSchemaVersion + uint32(len(m.Migrations)) +} diff --git a/packages/vm/core/migrations/migrations.go b/packages/vm/core/migrations/migrations.go deleted file mode 100644 index bdd20a3e06..0000000000 --- a/packages/vm/core/migrations/migrations.go +++ /dev/null @@ -1,23 +0,0 @@ -package migrations - -import ( - "github.com/iotaledger/hive.go/logger" - "github.com/iotaledger/wasp/packages/isc/coreutil" - "github.com/iotaledger/wasp/packages/kv" -) - -const BaseSchemaVersion = uint32(0) - -type Migration struct { - Contract *coreutil.ContractInfo - Apply func(state kv.KVStore, log *logger.Logger) error -} - -// Add new migrations to the end of this list, and they will be applied before -// creating the next block. -// The first migration on the list is applied when schema version = -// BaseSchemaVersion, and after applying each migration the schema version is -// incremented. -// Old migrations can be pruned; for each migration pruned increment -// BaseSchemaVersion by one. -var Migrations = []Migration{} diff --git a/packages/vm/core/testcore/accounts_test.go b/packages/vm/core/testcore/accounts_test.go index c21f562298..5e8a111736 100644 --- a/packages/vm/core/testcore/accounts_test.go +++ b/packages/vm/core/testcore/accounts_test.go @@ -818,8 +818,7 @@ func TestWithdrawDepositNativeTokens(t *testing.T) { err = ch2.DepositAssetsToL2(isc.NewAssets(1*isc.Million, iotago.NativeTokens{ {ID: v.nativeTokenID, Amount: big.NewInt(1)}, }), v.user) - require.Error(t, err) // TODO this check needs to be changed after the "accounting UTXO pruning fix" - testmisc.RequireErrorToBe(t, err, "request has been skipped") + require.NoError(t, err) }) } diff --git a/packages/vm/vmimpl/migrations.go b/packages/vm/vmimpl/migrations.go index 0f3df9b0e5..74a9800621 100644 --- a/packages/vm/vmimpl/migrations.go +++ b/packages/vm/vmimpl/migrations.go @@ -8,8 +8,8 @@ import ( "github.com/iotaledger/wasp/packages/vm/core/root" ) -func (vmctx *vmContext) runMigrations(chainState kv.KVStore, baseSchemaVersion uint32, allMigrations []migrations.Migration) { - latestSchemaVersion := baseSchemaVersion + uint32(len(allMigrations)) +func (vmctx *vmContext) runMigrations(chainState kv.KVStore, migrationScheme *migrations.MigrationScheme) { + latestSchemaVersion := migrationScheme.LatestSchemaVersion() if vmctx.task.AnchorOutput.StateIndex == 0 { // initializing new chain -- set the schema to latest version @@ -23,15 +23,15 @@ func (vmctx *vmContext) runMigrations(chainState kv.KVStore, baseSchemaVersion u withContractState(chainState, root.Contract, func(s kv.KVStore) { currentVersion = root.GetSchemaVersion(s) }) - if currentVersion < baseSchemaVersion { - panic(fmt.Sprintf("inconsistency: node with schema version %d is behind pruned migrations (should be >= %d)", currentVersion, baseSchemaVersion)) + if currentVersion < migrationScheme.BaseSchemaVersion { + panic(fmt.Sprintf("inconsistency: node with schema version %d is behind pruned migrations (should be >= %d)", currentVersion, migrationScheme.BaseSchemaVersion)) } if currentVersion > latestSchemaVersion { panic(fmt.Sprintf("inconsistency: node with schema version %d is ahead latest schema version (should be <= %d)", currentVersion, latestSchemaVersion)) } for currentVersion < latestSchemaVersion { - migration := allMigrations[currentVersion-baseSchemaVersion] + migration := migrationScheme.Migrations[currentVersion-migrationScheme.BaseSchemaVersion] withContractState(chainState, migration.Contract, func(s kv.KVStore) { err := migration.Apply(s, vmctx.task.Log) diff --git a/packages/vm/vmimpl/migrations_test.go b/packages/vm/vmimpl/migrations_test.go index 067971f3d6..5af92a6580 100644 --- a/packages/vm/vmimpl/migrations_test.go +++ b/packages/vm/vmimpl/migrations_test.go @@ -97,7 +97,10 @@ func TestMigrationsStateIndex0(t *testing.T) { require.EqualValues(t, 0, env.getSchemaVersion()) env.vmctx.withStateUpdate(func(chainState kv.KVStore) { - env.vmctx.runMigrations(chainState, 0, []migrations.Migration{env.panic, env.panic, env.panic}) + env.vmctx.runMigrations(chainState, &migrations.MigrationScheme{ + BaseSchemaVersion: 0, + Migrations: []migrations.Migration{env.panic, env.panic, env.panic}, + }) }) require.EqualValues(t, 3, env.getSchemaVersion()) @@ -109,7 +112,10 @@ func TestMigrationsStateIndex1(t *testing.T) { require.EqualValues(t, 0, env.getSchemaVersion()) env.vmctx.withStateUpdate(func(chainState kv.KVStore) { - env.vmctx.runMigrations(chainState, 0, []migrations.Migration{env.incCounter, env.incCounter, env.incCounter}) + env.vmctx.runMigrations(chainState, &migrations.MigrationScheme{ + BaseSchemaVersion: 0, + Migrations: []migrations.Migration{env.incCounter, env.incCounter, env.incCounter}, + }) }) require.EqualValues(t, 3, env.counter) @@ -122,7 +128,10 @@ func TestMigrationsStateIndex1Current1(t *testing.T) { env.setSchemaVersion(1) env.vmctx.withStateUpdate(func(chainState kv.KVStore) { - env.vmctx.runMigrations(chainState, 0, []migrations.Migration{env.panic, env.incCounter, env.incCounter}) + env.vmctx.runMigrations(chainState, &migrations.MigrationScheme{ + BaseSchemaVersion: 0, + Migrations: []migrations.Migration{env.panic, env.incCounter, env.incCounter}, + }) }) require.EqualValues(t, 2, env.counter) @@ -135,7 +144,10 @@ func TestMigrationsStateIndex1Current2Base1(t *testing.T) { env.setSchemaVersion(2) env.vmctx.withStateUpdate(func(chainState kv.KVStore) { - env.vmctx.runMigrations(chainState, 1, []migrations.Migration{env.panic, env.incCounter, env.incCounter}) + env.vmctx.runMigrations(chainState, &migrations.MigrationScheme{ + BaseSchemaVersion: 1, + Migrations: []migrations.Migration{env.panic, env.incCounter, env.incCounter}, + }) }) require.EqualValues(t, 2, env.counter) diff --git a/packages/vm/vmimpl/runtask.go b/packages/vm/vmimpl/runtask.go index 7428b872ed..90c4fa0b43 100644 --- a/packages/vm/vmimpl/runtask.go +++ b/packages/vm/vmimpl/runtask.go @@ -17,6 +17,7 @@ import ( "github.com/iotaledger/wasp/packages/vm/core/blocklog" "github.com/iotaledger/wasp/packages/vm/core/governance" "github.com/iotaledger/wasp/packages/vm/core/migrations" + "github.com/iotaledger/wasp/packages/vm/core/migrations/allmigrations" "github.com/iotaledger/wasp/packages/vm/vmexceptions" "github.com/iotaledger/wasp/packages/vm/vmtxbuilder" ) @@ -105,7 +106,8 @@ func (vmctx *vmContext) init(prevL1Commitment *state.L1Commitment) { vmctx.loadChainConfig() vmctx.withStateUpdate(func(chainState kv.KVStore) { - vmctx.runMigrations(chainState, migrations.BaseSchemaVersion, migrations.Migrations) + migrationScheme := vmctx.getMigrations() + vmctx.runMigrations(chainState, migrationScheme) }) // save the anchor tx ID of the current state @@ -140,6 +142,13 @@ func (vmctx *vmContext) init(prevL1Commitment *state.L1Commitment) { ) } +func (vmctx *vmContext) getMigrations() *migrations.MigrationScheme { + if vmctx.task.MigrationsOverride != nil { + return vmctx.task.MigrationsOverride + } + return allmigrations.DefaultScheme +} + func (vmctx *vmContext) getAnchorOutputSD() uint64 { // get the total L2 funds in accounting totalL2Funds := vmctx.loadTotalFungibleTokens() diff --git a/packages/vm/vmtask.go b/packages/vm/vmtask.go index 4c691c6847..080a31e90f 100644 --- a/packages/vm/vmtask.go +++ b/packages/vm/vmtask.go @@ -10,6 +10,7 @@ import ( "github.com/iotaledger/wasp/packages/kv/dict" "github.com/iotaledger/wasp/packages/state" "github.com/iotaledger/wasp/packages/vm/core/blocklog" + "github.com/iotaledger/wasp/packages/vm/core/migrations" "github.com/iotaledger/wasp/packages/vm/processors" ) @@ -32,6 +33,8 @@ type VMTask struct { EVMTracer *isc.EVMTracer EnableGasBurnLogging bool // for testing and Solo only + MigrationsOverride *migrations.MigrationScheme // for testing and Solo only + Log *logger.Logger } diff --git a/tools/cluster/tests/nodeconn_test.go b/tools/cluster/tests/nodeconn_test.go index f87b7965a2..6e9c85b205 100644 --- a/tools/cluster/tests/nodeconn_test.go +++ b/tools/cluster/tests/nodeconn_test.go @@ -23,6 +23,7 @@ import ( "github.com/iotaledger/wasp/packages/testutil" "github.com/iotaledger/wasp/packages/testutil/testlogger" "github.com/iotaledger/wasp/packages/testutil/testpeers" + "github.com/iotaledger/wasp/packages/vm/core/migrations/allmigrations" ) func createChain(t *testing.T) isc.ChainID { @@ -47,6 +48,7 @@ func createChain(t *testing.T) isc.ChainID { nil, utxoMap, utxoIDs, + allmigrations.DefaultScheme.LatestSchemaVersion(), ) require.NoError(t, err) _, err = layer1Client.PostTxAndWaitUntilConfirmation(originTx)