diff --git a/cmd/rollback/main.go b/cmd/rollback/main.go index f05e8256e..7bafdaf1a 100644 --- a/cmd/rollback/main.go +++ b/cmd/rollback/main.go @@ -10,10 +10,12 @@ import ( ) var ( - logLevel = flag.String("log-level", "INFO", "Logging level. Supported levels: DEBUG, INFO, WARN, ERROR, FATAL. Default logging level INFO.") - statePath = flag.String("state-path", "", "Path to node's state directory") - blockchainType = flag.String("blockchain-type", "mainnet", "Blockchain type: mainnet/testnet/stagenet") - height = flag.Uint64("height", 0, "Height to rollback") + logLevel = flag.String("log-level", "INFO", "Logging level. Supported levels: DEBUG, INFO, WARN, ERROR, FATAL. Default logging level INFO.") + statePath = flag.String("state-path", "", "Path to node's state directory") + blockchainType = flag.String("blockchain-type", "mainnet", "Blockchain type: mainnet/testnet/stagenet") + height = flag.Uint64("height", 0, "Height to rollback") + buildExtendedApi = flag.Bool("build-extended-api", false, "Builds extended API. Note that state must be reimported in case it wasn't imported with similar flag set") + buildStateHashes = flag.Bool("build-state-hashes", false, "Calculate and store state hashes for each block height.") ) func main() { @@ -31,31 +33,39 @@ func main() { return } params := state.DefaultStateParams() - state, err := state.NewState(*statePath, params, cfg) + params.BuildStateHashes = *buildStateHashes + params.StoreExtendedApiData = *buildExtendedApi + s, err := state.NewState(*statePath, params, cfg) if err != nil { zap.S().Error(err) return } + defer func() { + err = s.Close() + if err != nil { + zap.S().Errorf("Failed to close state: %v", err) + } + }() - curHeight, err := state.Height() + curHeight, err := s.Height() if err != nil { zap.S().Error(err) return } - zap.S().Infof("current height: %d", curHeight) + zap.S().Infof("Current height: %d", curHeight) - err = state.RollbackToHeight(*height) + err = s.RollbackToHeight(*height) if err != nil { zap.S().Error(err) return } - curHeight, err = state.Height() + curHeight, err = s.Height() if err != nil { zap.S().Error(err) return } - zap.S().Infof("current height: %d", curHeight) + zap.S().Infof("Current height: %d", curHeight) } diff --git a/pkg/proto/scripting.go b/pkg/proto/scripting.go index 69c930f47..fd744a59d 100644 --- a/pkg/proto/scripting.go +++ b/pkg/proto/scripting.go @@ -28,10 +28,9 @@ func (a *DataEntryScriptAction) ToProtobuf() *g.DataTransactionData_DataEntry { // TransferScriptAction is an action to emit transfer of asset. type TransferScriptAction struct { - Recipient Recipient - Amount int64 - Asset OptionalAsset - InvalidAsset bool + Recipient Recipient + Amount int64 + Asset OptionalAsset } func (a TransferScriptAction) scriptAction() {} @@ -332,9 +331,6 @@ func ValidateActions(actions []ScriptAction, restrictions ActionsValidationRestr if ta.Amount < 0 { return errors.New("negative transfer amount") } - if ta.InvalidAsset { - return errors.New("invalid asset") - } if restrictions.DisableSelfTransfers { if ta.Recipient.Address.Eq(restrictions.ScriptAddress) { return errors.New("transfers to DApp itself are forbidden since activation of RIDE V4") diff --git a/pkg/proto/types.go b/pkg/proto/types.go index 83d1393fe..55b21209b 100644 --- a/pkg/proto/types.go +++ b/pkg/proto/types.go @@ -3232,16 +3232,18 @@ type FullScriptTransfer struct { Asset OptionalAsset Recipient Recipient Sender Address + SenderPK crypto.PublicKey Timestamp uint64 ID *crypto.Digest } -func NewFullScriptTransfer(action *TransferScriptAction, sender Address, tx *InvokeScriptWithProofs) (*FullScriptTransfer, error) { +func NewFullScriptTransfer(action *TransferScriptAction, sender Address, senderPK crypto.PublicKey, tx *InvokeScriptWithProofs) (*FullScriptTransfer, error) { return &FullScriptTransfer{ Amount: uint64(action.Amount), Asset: action.Asset, Recipient: action.Recipient, Sender: sender, + SenderPK: senderPK, Timestamp: tx.Timestamp, ID: tx.ID, }, nil diff --git a/pkg/ride/converters.go b/pkg/ride/converters.go index f8331184e..67528f83f 100644 --- a/pkg/ride/converters.go +++ b/pkg/ride/converters.go @@ -956,7 +956,7 @@ func scriptTransferToObject(tr *proto.FullScriptTransfer) rideObject { r["version"] = rideUnit{} r["id"] = rideBytes(tr.ID.Bytes()) r["sender"] = rideAddress(tr.Sender) - r["senderPublicKey"] = rideUnit{} + r["senderPublicKey"] = rideBytes(common.Dup(tr.SenderPK.Bytes())) r["recipient"] = rideRecipient(tr.Recipient) r["assetId"] = optionalAsset(tr.Asset) r["amount"] = rideInt(tr.Amount) @@ -1211,15 +1211,20 @@ func convertToAction(env RideEnvironment, obj rideType) (proto.ScriptAction, err return nil, errors.Wrap(err, "failed to convert ScriptTransfer to ScriptAction") } asset, err := optionalAssetProperty(obj, "asset") - invalidAsset := false + // On asset ID conversion error we return empty action as in Scala + // See example on MainNet: transaction (https://wavesexplorer.com/tx/AUpiEr49Jo43Q9zXKkNN23rstiq87hguvhfQqV8ov9uQ) + // and script (https://wavesexplorer.com/tx/Bp1oieWHWpLz8vBFZui9tY1oDTAKUPTrBAGcwfRe9q5K) if err != nil { - invalidAsset = true + return &proto.TransferScriptAction{ + Recipient: recipient, + Amount: 0, + Asset: proto.OptionalAsset{Present: false}, + }, nil } return &proto.TransferScriptAction{ - Recipient: recipient, - Amount: int64(amount), - Asset: asset, - InvalidAsset: invalidAsset, + Recipient: recipient, + Amount: int64(amount), + Asset: asset, }, nil case "SponsorFee": id, err := digestProperty(obj, "assetId") diff --git a/pkg/ride/tree_evaluation_test.go b/pkg/ride/tree_evaluation_test.go index f6d615426..e8965cb93 100644 --- a/pkg/ride/tree_evaluation_test.go +++ b/pkg/ride/tree_evaluation_test.go @@ -3381,3 +3381,137 @@ func TestRecipientAddressToString(t *testing.T) { require.True(t, ok) assert.True(t, r.Result()) } + +func TestScriptPaymentPublicKey(t *testing.T) { + pk := crypto.MustPublicKeyFromBase58("7gYTeHxHZ2NRQdNpa6DHAxQY4K5LS6bezcsMQcUhYuo1") + addr := proto.MustAddressFromPublicKey(proto.MainNetScheme, pk) + asset, err := proto.NewOptionalAssetFromString("5F4PshPwzE8sQeesDPzjJN45CFVnAnqCUHJcmi7kZq22") + require.NoError(t, err) + rcp := proto.NewRecipientFromAddress(addr) + action := &proto.TransferScriptAction{ + Recipient: rcp, + Amount: 12345, + Asset: *asset, + } + id := crypto.MustDigestFromBase58("9vt45R9y63Xwcseat59BchUjfJGHSuN5LeTK6Pd6cFUM") + tx := &proto.InvokeScriptWithProofs{ + Type: proto.InvokeScriptTransaction, + Version: 1, + ID: &id, + ChainID: proto.MainNetScheme, + SenderPK: pk, + ScriptRecipient: rcp, + Payments: proto.ScriptPayments{}, + FeeAsset: proto.OptionalAsset{}, + Fee: 1300000, + Timestamp: 1599565088614, + } + + tr, _ := proto.NewFullScriptTransfer(action, addr, pk, tx) + env := &MockRideEnvironment{ + schemeFunc: func() byte { + return proto.MainNetScheme + }, + transactionFunc: func() rideObject { + return scriptTransferToObject(tr) + }, + checkMessageLengthFunc: v3check, + } + + code := "AQQAAAAGc2VuZGVyCQACWAAAAAEICQEAAAAUYWRkcmVzc0Zyb21QdWJsaWNLZXkAAAABCAUAAAACdHgAAAAPc2VuZGVyUHVibGljS2V5AAAABWJ5dGVzCQAAAAAAAAICAAAAIzNQNjFiNnRlMmZ2akw3YWdLSHFOY0NrcHV0Z1lzNjV4dzVSBQAAAAZzZW5kZXJlKXM0" + src, err := base64.StdEncoding.DecodeString(code) + require.NoError(t, err) + tree, err := Parse(src) + require.NoError(t, err) + assert.NotNil(t, tree) + + res, err := CallVerifier(env, tree) + require.NoError(t, err) + r, ok := res.(ScriptResult) + require.True(t, ok) + require.True(t, r.Result()) +} + +func TestInvalidAssetInTransferScriptAction(t *testing.T) { + txID, err := crypto.NewDigestFromBase58("AUpiEr49Jo43Q9zXKkNN23rstiq87hguvhfQqV8ov9uQ") + require.NoError(t, err) + proofs := proto.NewProofs() + sender, err := crypto.NewPublicKeyFromBase58("Hjd6p3ArqjnQAsejFwu7JcQciVVx9RaQhtMfGBCAi76z") + require.NoError(t, err) + address, err := proto.NewAddressFromString("3P8FF73N7ZvvNJ34vnJ3h9Tfmh7oQCnRz8E") + require.NoError(t, err) + recipient := proto.NewRecipientFromAddress(address) + arguments := proto.Arguments{} + call := proto.FunctionCall{ + Default: false, + Name: "swapRKMTToWAVES", + Arguments: arguments, + } + asset, err := proto.NewOptionalAssetFromString("2fCdmsn6maErwtLuzxoUrCBkh2vx5SvXtMKAJtN4YBgd") + require.NoError(t, err) + tx := &proto.InvokeScriptWithProofs{ + Type: proto.InvokeScriptTransaction, + Version: 1, + ID: &txID, + Proofs: proofs, + ChainID: proto.MainNetScheme, + SenderPK: sender, + ScriptRecipient: recipient, + FunctionCall: call, + Payments: proto.ScriptPayments{proto.ScriptPayment{Amount: 1000, Asset: *asset}}, + FeeAsset: proto.OptionalAsset{}, + Fee: 500000, + Timestamp: 1609698441420, + } + env := &MockRideEnvironment{ + schemeFunc: func() byte { + return proto.MainNetScheme + }, + thisFunc: func() rideType { + return rideAddress(address) + }, + transactionFunc: func() rideObject { + obj, err := transactionToObject(proto.MainNetScheme, tx) + require.NoError(t, err) + return obj + }, + invocationFunc: func() rideObject { + obj, err := invocationToObject(3, proto.MainNetScheme, tx) + require.NoError(t, err) + return obj + }, + checkMessageLengthFunc: v3check, + } + + code := "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" + src, err := base64.StdEncoding.DecodeString(code) + require.NoError(t, err) + tree, err := Parse(src) + require.NoError(t, err) + assert.NotNil(t, tree) + res, err := CallFunction(env, tree, "swapRKMTToWAVES", arguments) + require.NoError(t, err) + r, ok := res.(DAppResult) + require.True(t, ok) + require.True(t, r.res) + + sr, err := proto.NewScriptResult(r.actions, proto.ScriptErrorMessage{}) + require.NoError(t, err) + + expectedTransfers := []*proto.TransferScriptAction{ + { + Recipient: proto.NewRecipientFromAddress(proto.MustAddressFromString("3P8FF73N7ZvvNJ34vnJ3h9Tfmh7oQCnRz8E")), + Amount: 0, + Asset: proto.OptionalAsset{Present: false}, + }, + } + expectedResult := &proto.ScriptResult{ + DataEntries: make([]*proto.DataEntryScriptAction, 0), + Transfers: expectedTransfers, + Issues: make([]*proto.IssueScriptAction, 0), + Reissues: make([]*proto.ReissueScriptAction, 0), + Burns: make([]*proto.BurnScriptAction, 0), + Sponsorships: make([]*proto.SponsorshipScriptAction, 0), + } + assert.Equal(t, expectedResult, sr) +} diff --git a/pkg/state/invoke_applier.go b/pkg/state/invoke_applier.go index cf67a2a2d..7690c75c9 100644 --- a/pkg/state/invoke_applier.go +++ b/pkg/state/invoke_applier.go @@ -303,7 +303,7 @@ func (ia *invokeApplier) fallibleValidation(tx *proto.InvokeScriptWithProofs, in } sender = addr } - fullTr, err := proto.NewFullScriptTransfer(a, *sender, tx) + fullTr, err := proto.NewFullScriptTransfer(a, *sender, info.scriptPK, tx) if err != nil { return proto.DAppError, info.failedChanges, errors.Wrap(err, "failed to convert transfer to full script transfer") }