diff --git a/CHANGELOG.md b/CHANGELOG.md index 747f4705b2..3fa833c173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ See [RELEASE](./RELEASE.md) for workflow instructions. * [#6099](https://github.com/spacemeshos/go-spacemesh/pull/6099) Adds new metrics to the API to provide insights into the performance and behavior of the node's APIs. +### Features +* [#6112](https://github.com/spacemeshos/go-spacemesh/pull/6112) Adds vesting, vault, and drain vault contents to the + v2alpha2 Transaction API. Fixes the 'unspecified' transaction type. + ## Release v1.6.1 ### Improvements diff --git a/api/grpcserver/v2alpha1/transaction.go b/api/grpcserver/v2alpha1/transaction.go index 3ec4e26610..a1c8edd13e 100644 --- a/api/grpcserver/v2alpha1/transaction.go +++ b/api/grpcserver/v2alpha1/transaction.go @@ -172,10 +172,11 @@ func (s *TransactionService) ParseTransaction( t.MaxGas = header.MaxGas t.GasPrice = header.GasPrice t.MaxSpend = header.MaxSpend - contents, err := toTxContents(raw.Raw) + contents, txType, err := toTxContents(raw.Raw) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } + t.Type = txType t.Contents = contents } @@ -329,11 +330,12 @@ func toTx(tx *types.MeshTransaction, result *types.TransactionResult, t.GasPrice = tx.GasPrice t.MaxSpend = tx.MaxSpend - contents, err := toTxContents(tx.Raw) + contents, txType, err := toTxContents(tx.Raw) if err != nil { return nil } t.Contents = contents + t.Type = txType } if includeResult && result != nil { @@ -412,12 +414,15 @@ func decodeTxArgs(decoder *scale.Decoder) (uint8, *core.Address, scale.Encodable var templateAddress *core.Address var handler core.Handler - if method == core.MethodSpawn { + switch method { + case core.MethodSpawn: templateAddress = &core.Address{} if _, err := templateAddress.DecodeScale(decoder); err != nil { return 0, nil, nil, fmt.Errorf("%w failed to decode template address %w", core.ErrMalformed, err) } - } else { + case vesting.MethodDrainVault: + templateAddress = &vesting.TemplateAddress + default: templateAddress = &wallet.TemplateAddress } @@ -442,12 +447,18 @@ func decodeTxArgs(decoder *scale.Decoder) (uint8, *core.Address, scale.Encodable return method, templateAddress, args, nil } -func toTxContents(rawTx []byte) (*spacemeshv2alpha1.TransactionContents, error) { - method, template, txArgs, err := decodeTxArgs(scale.NewDecoder(bytes.NewReader(rawTx))) +func toTxContents(rawTx []byte) (*spacemeshv2alpha1.TransactionContents, + spacemeshv2alpha1.Transaction_TransactionType, error, +) { + res := &spacemeshv2alpha1.TransactionContents{} + txType := spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_UNSPECIFIED + + r := bytes.NewReader(rawTx) + method, template, txArgs, err := decodeTxArgs(scale.NewDecoder(r)) if err != nil { - return nil, err + return res, txType, err } - res := &spacemeshv2alpha1.TransactionContents{} + switch method { case core.MethodSpawn: switch *template { @@ -458,6 +469,7 @@ func toTxContents(rawTx []byte) (*spacemeshv2alpha1.TransactionContents, error) Pubkey: args.PublicKey.String(), }, } + txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_SINGLE_SIG_SPAWN case multisig.TemplateAddress: args := txArgs.(*multisig.SpawnArguments) contents := &spacemeshv2alpha1.TransactionContents_MultiSigSpawn{ @@ -470,6 +482,32 @@ func toTxContents(rawTx []byte) (*spacemeshv2alpha1.TransactionContents, error) contents.MultiSigSpawn.Pubkey[i] = args.PublicKeys[i].String() } res.Contents = contents + txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_MULTI_SIG_SPAWN + case vesting.TemplateAddress: + args := txArgs.(*multisig.SpawnArguments) + contents := &spacemeshv2alpha1.TransactionContents_VestingSpawn{ + VestingSpawn: &spacemeshv2alpha1.ContentsMultiSigSpawn{ + Required: uint32(args.Required), + }, + } + contents.VestingSpawn.Pubkey = make([]string, len(args.PublicKeys)) + for i := range args.PublicKeys { + contents.VestingSpawn.Pubkey[i] = args.PublicKeys[i].String() + } + res.Contents = contents + txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_VESTING_SPAWN + case vault.TemplateAddress: + args := txArgs.(*vault.SpawnArguments) + res.Contents = &spacemeshv2alpha1.TransactionContents_VaultSpawn{ + VaultSpawn: &spacemeshv2alpha1.ContentsVaultSpawn{ + Owner: args.Owner.String(), + TotalAmount: args.TotalAmount, + InitialUnlockAmount: args.InitialUnlockAmount, + VestingStart: args.VestingStart.Uint32(), + VestingEnd: args.VestingEnd.Uint32(), + }, + } + txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_VAULT_SPAWN } case core.MethodSpend: args := txArgs.(*wallet.SpendArguments) @@ -479,7 +517,21 @@ func toTxContents(rawTx []byte) (*spacemeshv2alpha1.TransactionContents, error) Amount: args.Amount, }, } + txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_SINGLE_SIG_SEND + if r.Len() > types.EdSignatureSize { + txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_MULTI_SIG_SEND + } + case vesting.MethodDrainVault: + args := txArgs.(*vesting.DrainVaultArguments) + res.Contents = &spacemeshv2alpha1.TransactionContents_DrainVault{ + DrainVault: &spacemeshv2alpha1.ContentsDrainVault{ + Vault: args.Vault.String(), + Destination: args.Destination.String(), + Amount: args.Amount, + }, + } + txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_DRAIN_VAULT } - return res, nil + return res, txType, nil } diff --git a/api/grpcserver/v2alpha1/transaction_test.go b/api/grpcserver/v2alpha1/transaction_test.go index aec1222615..3128b470d5 100644 --- a/api/grpcserver/v2alpha1/transaction_test.go +++ b/api/grpcserver/v2alpha1/transaction_test.go @@ -22,7 +22,12 @@ import ( vm "github.com/spacemeshos/go-spacemesh/genvm" "github.com/spacemeshos/go-spacemesh/genvm/core" "github.com/spacemeshos/go-spacemesh/genvm/sdk" + multisig2 "github.com/spacemeshos/go-spacemesh/genvm/sdk/multisig" + "github.com/spacemeshos/go-spacemesh/genvm/sdk/vesting" "github.com/spacemeshos/go-spacemesh/genvm/sdk/wallet" + "github.com/spacemeshos/go-spacemesh/genvm/templates/multisig" + "github.com/spacemeshos/go-spacemesh/genvm/templates/vault" + vesting2 "github.com/spacemeshos/go-spacemesh/genvm/templates/vesting" pubsubmocks "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" @@ -474,3 +479,230 @@ func newTx(nonce uint64, recipient types.Address, signer *signing.EdSigner) *typ } return &tx } + +func TestToTxContents(t *testing.T) { + t.Parallel() + + t.Run("singlesig spawn", func(t *testing.T) { + t.Parallel() + + signer, err := signing.NewEdSigner() + require.NoError(t, err) + tx := newTx(0, types.Address{}, signer) + + contents, txType, err := toTxContents(tx.Raw) + require.NoError(t, err) + require.NotNil(t, contents.GetSingleSigSpawn()) + require.Nil(t, contents.GetSend()) + require.Equal(t, spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_SINGLE_SIG_SPAWN, txType) + }) + + t.Run("singlesig send", func(t *testing.T) { + t.Parallel() + + signer, err := signing.NewEdSigner() + require.NoError(t, err) + tx := newTx(1, types.Address{}, signer) + + contents, txType, err := toTxContents(tx.Raw) + require.NoError(t, err) + require.NotNil(t, contents.GetSend()) + require.Nil(t, contents.GetSingleSigSpawn()) + require.Equal(t, spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_SINGLE_SIG_SEND, txType) + }) + + t.Run("multisig spawn", func(t *testing.T) { + t.Parallel() + + var pubs []ed25519.PublicKey + pks := make([]ed25519.PrivateKey, 0, 3) + for i := 0; i < 3; i++ { + pub, pk, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + pubs = append(pubs, pub) + pks = append(pks, pk) + } + + var agg *multisig2.Aggregator + for i := 0; i < len(pks); i++ { + part := multisig2.SelfSpawn(uint8(i), pks[i], multisig.TemplateAddress, 1, pubs, types.Nonce(1)) + if agg == nil { + agg = part + } else { + agg.Add(*part.Part(uint8(i))) + } + } + rawTx := agg.Raw() + + contents, txType, err := toTxContents(rawTx) + require.NoError(t, err) + require.NotNil(t, contents.GetMultiSigSpawn()) + require.Nil(t, contents.GetSend()) + require.Nil(t, contents.GetSingleSigSpawn()) + require.Nil(t, contents.GetVestingSpawn()) + require.Nil(t, contents.GetVaultSpawn()) + require.Nil(t, contents.GetDrainVault()) + require.Equal(t, spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_MULTI_SIG_SPAWN, txType) + }) + + t.Run("multisig send", func(t *testing.T) { + t.Parallel() + + var pubs []ed25519.PublicKey + pks := make([]ed25519.PrivateKey, 0, 3) + for i := 0; i < 3; i++ { + pub, pk, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + pubs = append(pubs, pub) + pks = append(pks, pk) + } + + to := wallet.Address(pubs[0]) + + var agg *multisig2.Aggregator + for i := 0; i < len(pks); i++ { + part := multisig2.Spend(uint8(i), pks[i], multisig.TemplateAddress, to, 100, types.Nonce(1)) + if agg == nil { + agg = part + } else { + agg.Add(*part.Part(uint8(i))) + } + } + rawTx := agg.Raw() + + contents, txType, err := toTxContents(rawTx) + require.NoError(t, err) + require.NotNil(t, contents.GetSend()) + require.Nil(t, contents.GetMultiSigSpawn()) + require.Nil(t, contents.GetSingleSigSpawn()) + require.Nil(t, contents.GetVestingSpawn()) + require.Nil(t, contents.GetVaultSpawn()) + require.Nil(t, contents.GetDrainVault()) + require.Equal(t, spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_MULTI_SIG_SEND, txType) + }) + + t.Run("vault spawn", func(t *testing.T) { + t.Parallel() + + var pubs []ed25519.PublicKey + pks := make([]ed25519.PrivateKey, 0, 3) + for i := 0; i < 3; i++ { + pub, pk, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + pubs = append(pubs, pub) + pks = append(pks, pk) + } + + owner := wallet.Address(pubs[0]) + vaultArgs := &vault.SpawnArguments{ + Owner: owner, + InitialUnlockAmount: uint64(1000), + TotalAmount: uint64(1001), + VestingStart: 105120, + VestingEnd: 4 * 105120, + } + vaultAddr := core.ComputePrincipal(vault.TemplateAddress, vaultArgs) + + var agg *multisig2.Aggregator + for i := 0; i < len(pks); i++ { + part := multisig2.Spawn(uint8(i), pks[i], vaultAddr, vault.TemplateAddress, vaultArgs, types.Nonce(0)) + if agg == nil { + agg = part + } else { + agg.Add(*part.Part(uint8(i))) + } + } + rawTx := agg.Raw() + + contents, txType, err := toTxContents(rawTx) + require.NoError(t, err) + require.NotNil(t, contents.GetVaultSpawn()) + require.Nil(t, contents.GetMultiSigSpawn()) + require.Nil(t, contents.GetSingleSigSpawn()) + require.Nil(t, contents.GetVestingSpawn()) + require.Nil(t, contents.GetSend()) + require.Nil(t, contents.GetDrainVault()) + require.Equal(t, vaultArgs.Owner.String(), contents.GetVaultSpawn().Owner) + require.Equal(t, vaultArgs.InitialUnlockAmount, contents.GetVaultSpawn().InitialUnlockAmount) + require.Equal(t, vaultArgs.TotalAmount, contents.GetVaultSpawn().TotalAmount) + require.Equal(t, vaultArgs.VestingStart.Uint32(), contents.GetVaultSpawn().VestingStart) + require.Equal(t, vaultArgs.VestingEnd.Uint32(), contents.GetVaultSpawn().VestingEnd) + require.Equal(t, spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_VAULT_SPAWN, txType) + }) + + t.Run("drain vault", func(t *testing.T) { + t.Parallel() + + var pubs [][]byte + pks := make([]ed25519.PrivateKey, 0, 3) + for i := 0; i < 3; i++ { + pub, pk, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + pubs = append(pubs, pub) + pks = append(pks, pk) + } + + principal := multisig2.Address(multisig.TemplateAddress, 3, pubs...) + to := wallet.Address(pubs[1]) + vaultAddr := wallet.Address(pubs[2]) + + agg := vesting.DrainVault( + 0, + pks[0], + principal, + vaultAddr, + to, + 100, + types.Nonce(1)) + for i := 1; i < len(pks); i++ { + part := vesting.DrainVault(uint8(i), pks[i], principal, vaultAddr, to, 100, types.Nonce(1)) + agg.Add(*part.Part(uint8(i))) + } + rawTx := agg.Raw() + + contents, txType, err := toTxContents(rawTx) + require.NoError(t, err) + require.NotNil(t, contents.GetDrainVault()) + require.Nil(t, contents.GetMultiSigSpawn()) + require.Nil(t, contents.GetSingleSigSpawn()) + require.Nil(t, contents.GetVestingSpawn()) + require.Nil(t, contents.GetSend()) + require.Nil(t, contents.GetVaultSpawn()) + require.Equal(t, vaultAddr.String(), contents.GetDrainVault().Vault) + require.Equal(t, spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_DRAIN_VAULT, txType) + }) + + t.Run("multisig vesting spawn", func(t *testing.T) { + t.Parallel() + + var pubs []ed25519.PublicKey + pks := make([]ed25519.PrivateKey, 0, 3) + for i := 0; i < 3; i++ { + pub, pk, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + pubs = append(pubs, pub) + pks = append(pks, pk) + } + + var agg *multisig2.Aggregator + for i := 0; i < len(pks); i++ { + part := multisig2.SelfSpawn(uint8(i), pks[i], vesting2.TemplateAddress, 1, pubs, types.Nonce(1)) + if agg == nil { + agg = part + } else { + agg.Add(*part.Part(uint8(i))) + } + } + rawTx := agg.Raw() + + contents, txType, err := toTxContents(rawTx) + require.NoError(t, err) + require.NotNil(t, contents.GetVestingSpawn()) + require.Nil(t, contents.GetSend()) + require.Nil(t, contents.GetSingleSigSpawn()) + require.Nil(t, contents.GetMultiSigSpawn()) + require.Nil(t, contents.GetVaultSpawn()) + require.Nil(t, contents.GetDrainVault()) + require.Equal(t, spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_VESTING_SPAWN, txType) + }) +} diff --git a/go.mod b/go.mod index 76db186c0b..827d640da8 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/seehuhn/mt19937 v1.0.0 github.com/slok/go-http-metrics v0.12.0 - github.com/spacemeshos/api/release/go v1.49.0 + github.com/spacemeshos/api/release/go v1.50.0 github.com/spacemeshos/economics v0.1.3 github.com/spacemeshos/fixed v0.1.1 github.com/spacemeshos/go-scale v1.2.0 diff --git a/go.sum b/go.sum index d8cfe3f2fb..45e85ecd92 100644 --- a/go.sum +++ b/go.sum @@ -604,8 +604,8 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spacemeshos/api/release/go v1.49.0 h1:1hRPztB5aTX1ebnjS8Fh86Q2JQPDzM4+MbU40rrbK5A= -github.com/spacemeshos/api/release/go v1.49.0/go.mod h1:8pxGN6/di8iBpQReiOgY+Cppi7bhJ+qJ3QiRQtJfoag= +github.com/spacemeshos/api/release/go v1.50.0 h1:M7Usg/LxymscwqYO7/Doyb+sU4lS1e+JIsSgqTDGk/0= +github.com/spacemeshos/api/release/go v1.50.0/go.mod h1:PvgDpjfwkZLVVNExYG7wDNzgMqT3p+ppfTU2UESSF9U= github.com/spacemeshos/economics v0.1.3 h1:ACkq3mTebIky4Zwbs9SeSSRZrUCjU/Zk0wq9Z0BTh2A= github.com/spacemeshos/economics v0.1.3/go.mod h1:FH7u0FzTIm6Kpk+X5HOZDvpkgNYBKclmH86rVwYaDAo= github.com/spacemeshos/fixed v0.1.1 h1:N1y4SUpq1EV+IdJrWJwUCt1oBFzeru/VKVcBsvPc2Fk=