Skip to content

Commit

Permalink
Merge pull request #6542 from spacemeshos/athena-support-multisig-in-api
Browse files Browse the repository at this point in the history
[athena-poc] support multisig in transactions API
  • Loading branch information
poszu authored Dec 13, 2024
2 parents ce8a6d2 + 7703d8b commit 135ca11
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 213 deletions.
118 changes: 73 additions & 45 deletions api/grpcserver/v2alpha1/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/spacemeshos/go-spacemesh/sql/transactions"
"github.com/spacemeshos/go-spacemesh/system"
"github.com/spacemeshos/go-spacemesh/vm/core"
"github.com/spacemeshos/go-spacemesh/vm/templates/multisig"
"github.com/spacemeshos/go-spacemesh/vm/templates/wallet"
)

Expand Down Expand Up @@ -174,7 +175,7 @@ func (s *TransactionService) ParseTransaction(
t.MaxGas = header.MaxGas
t.GasPrice = header.GasPrice
t.MaxSpend = header.MaxSpend
contents, txType, err := toTxContents(raw.Raw)
contents, txType, err := toTxContents(raw.Raw, header)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
Expand Down Expand Up @@ -327,7 +328,7 @@ func (s *TransactionService) toTx(
t.GasPrice = tx.GasPrice
t.MaxSpend = tx.MaxSpend

contents, txType, err := toTxContents(tx.Raw)
contents, txType, err := toTxContents(tx.Raw, tx.TxHeader)
if err != nil {
return nil
}
Expand Down Expand Up @@ -403,61 +404,88 @@ func (s *TransactionService) convertTxState(
}
}

func decodeTxArgs(decoder *scale.Decoder) (any, *core.Address, error) {
func toTxContents(rawTx []byte, header *types.TxHeader) (
*spacemeshv2alpha1.TransactionContents, spacemeshv2alpha1.Transaction_TransactionType, error,
) {
res := &spacemeshv2alpha1.TransactionContents{}
txType := spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_UNSPECIFIED

var tx core.Tx
_, err := tx.DecodeScale(decoder)
_, err := tx.DecodeScale(scale.NewDecoder(bytes.NewReader(rawTx)))
if err != nil {
return nil, nil, fmt.Errorf("%w: decoding TX: %w", core.ErrMalformed, err)
return nil, txType, fmt.Errorf("%w: decoding TX: %w", core.ErrMalformed, err)
}

var payload athcon.Payload
err = gossamerScale.Unmarshal(tx.Payload, &payload)
if err != nil {
return nil, nil, fmt.Errorf("%w: tx payload: %w", core.ErrMalformed, err)
return nil, txType, fmt.Errorf("%w: tx payload: %w", core.ErrMalformed, err)
}

txArgs, err := wallet.ParseArgs(payload)
if err != nil {
return nil, nil, fmt.Errorf("%w: decoding TX args: %w", core.ErrMalformed, err)
}
return txArgs, &wallet.TemplateAddress, nil
}

func toTxContents(rawTx []byte) (*spacemeshv2alpha1.TransactionContents,
spacemeshv2alpha1.Transaction_TransactionType, error,
) {
res := &spacemeshv2alpha1.TransactionContents{}
txType := spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_UNSPECIFIED

r := bytes.NewReader(rawTx)
txArgs, _, err := decodeTxArgs(scale.NewDecoder(r))
if err != nil {
return res, txType, err
}

switch args := txArgs.(type) {
case *wallet.SpawnArgs:
res.Contents = &spacemeshv2alpha1.TransactionContents_SingleSigSpawn{
SingleSigSpawn: &spacemeshv2alpha1.ContentsSingleSigSpawn{
Pubkey: signing.NewPublicKey(args.Pubkey[:]).String(),
},
switch header.TemplateAddress {
case wallet.TemplateAddress:
txArgs, err := wallet.ParseArgs(payload)
if err != nil {
return nil, txType, fmt.Errorf("%w: decoding TX args: %w", core.ErrMalformed, err)
}
switch args := txArgs.(type) {
case *wallet.SpawnArgs:
res.Contents = &spacemeshv2alpha1.TransactionContents_SingleSigSpawn{
SingleSigSpawn: &spacemeshv2alpha1.ContentsSingleSigSpawn{
Pubkey: signing.NewPublicKey(args.Pubkey[:]).String(),
},
}
txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_SINGLE_SIG_SPAWN
case *wallet.SpendArgs:
res.Contents = &spacemeshv2alpha1.TransactionContents_Send{
Send: &spacemeshv2alpha1.ContentsSend{
Destination: args.To.String(),
Amount: args.Amount,
},
}
txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_SINGLE_SIG_SEND
case *wallet.DeployArgs:
res.Contents = &spacemeshv2alpha1.TransactionContents_Deploy{
Deploy: &spacemeshv2alpha1.ContentsDeploy{
Template: core.TemplateAddress(args.Code).String(),
},
}
txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_DEPLOY
}
txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_SINGLE_SIG_SPAWN
case *wallet.SpendArgs:
res.Contents = &spacemeshv2alpha1.TransactionContents_Send{
Send: &spacemeshv2alpha1.ContentsSend{
Destination: args.To.String(),
Amount: args.Amount,
},
case multisig.TemplateAddress:
txArgs, err := multisig.ParseArgs(payload)
if err != nil {
return nil, txType, fmt.Errorf("%w: decoding TX args: %w", core.ErrMalformed, err)
}
txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_SINGLE_SIG_SEND
case *wallet.DeployArgs:
res.Contents = &spacemeshv2alpha1.TransactionContents_Deploy{
Deploy: &spacemeshv2alpha1.ContentsDeploy{
Template: core.TemplateAddress(args.Code).String(),
},
switch args := txArgs.(type) {
case *multisig.SpawnArguments:
pubs := make([]string, 0, len(args.PublicKeys))
for _, pub := range args.PublicKeys {
pubs = append(pubs, pub.String())
}
res.Contents = &spacemeshv2alpha1.TransactionContents_MultiSigSpawn{
MultiSigSpawn: &spacemeshv2alpha1.ContentsMultiSigSpawn{
Required: uint32(args.Required),
Pubkey: pubs,
},
}
txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_MULTI_SIG_SPAWN
case *multisig.SpendArguments:
res.Contents = &spacemeshv2alpha1.TransactionContents_Send{
Send: &spacemeshv2alpha1.ContentsSend{
Destination: args.To.String(),
Amount: args.Amount,
},
}
txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_MULTI_SIG_SEND
case *multisig.DeployArguments:
res.Contents = &spacemeshv2alpha1.TransactionContents_Deploy{
Deploy: &spacemeshv2alpha1.ContentsDeploy{
Template: core.TemplateAddress(args.Code).String(),
},
}
txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_DEPLOY
}
txType = spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_DEPLOY
}

return res, txType, nil
Expand Down
136 changes: 72 additions & 64 deletions api/grpcserver/v2alpha1/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import (
"github.com/spacemeshos/go-spacemesh/vm"
"github.com/spacemeshos/go-spacemesh/vm/core"
"github.com/spacemeshos/go-spacemesh/vm/sdk"
sdkmultisig "github.com/spacemeshos/go-spacemesh/vm/sdk/multisig"
"github.com/spacemeshos/go-spacemesh/vm/sdk/wallet"
"github.com/spacemeshos/go-spacemesh/vm/templates/multisig"
walletTemplate "github.com/spacemeshos/go-spacemesh/vm/templates/wallet"
)

Expand Down Expand Up @@ -580,7 +582,7 @@ func TestToTxContents(t *testing.T) {
require.NoError(t, err)
tx := newTx(t, 0, types.Address{}, signer)

contents, txType, err := toTxContents(tx.Raw)
contents, txType, err := toTxContents(tx.Raw, &types.TxHeader{TemplateAddress: walletTemplate.TemplateAddress})
require.NoError(t, err)
require.NotNil(t, contents.GetSingleSigSpawn())
require.Nil(t, contents.GetSend())
Expand All @@ -594,84 +596,90 @@ func TestToTxContents(t *testing.T) {
require.NoError(t, err)
tx := newTx(t, 1, types.Address{}, signer)

contents, txType, err := toTxContents(tx.Raw)
contents, txType, err := toTxContents(tx.Raw, &types.TxHeader{TemplateAddress: walletTemplate.TemplateAddress})
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.Skip("multisig spawn is not supported yet")
// 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)
// }
t.Parallel()

// 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()
var (
pubs []core.PublicKey
pubStrs []string
pks []ed25519.PrivateKey
)

for i := 0; i < 3; i++ {
pub, pk, err := ed25519.GenerateKey(nil)
require.NoError(t, err)
pks = append(pks, pk)
p := core.PublicKey(pub)
pubs = append(pubs, p)
pubStrs = append(pubStrs, p.String())
}

// 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)
tx, err := sdkmultisig.Spawn(multisig.TemplateAddress, 2, pubs, 0)
require.NoError(t, err)
agg := sdkmultisig.NewSignatureAggregator(tx)
for i := range 2 {
sig := core.SignRawTx(tx, types.Hash20{}, pks[i])
agg.Add(uint8(i), core.Signature(sig))
}
rawTx := agg.Raw()
contents, txType, err := toTxContents(rawTx, &types.TxHeader{TemplateAddress: multisig.TemplateAddress})
require.NoError(t, err)
require.NotNil(t, contents.GetMultiSigSpawn())
require.Equal(t, &spacemeshv2alpha1.ContentsMultiSigSpawn{
Required: 2,
Pubkey: pubStrs,
}, 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.Skip("multisig send is not supported yet")
// 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)
// }
t.Parallel()

// to, err := wallet.Address(*signing.NewPublicKey(pubs[0]))
// require.NoError(t, err)
var (
pks []ed25519.PrivateKey
to = types.RandomAddress(t)
from = types.RandomAddress(t)
)
for i := 0; i < 3; i++ {
_, pk, err := ed25519.GenerateKey(nil)
require.NoError(t, err)
pks = append(pks, pk)
}

// 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()
tx, err := sdkmultisig.Spend(from, to, 100, 1)
require.NoError(t, err)
agg := sdkmultisig.NewSignatureAggregator(tx)
for i := range 2 {
sig := core.SignRawTx(tx, types.Hash20{}, pks[i])
agg.Add(uint8(i), core.Signature(sig))
}
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)
contents, txType, err := toTxContents(rawTx, &types.TxHeader{TemplateAddress: multisig.TemplateAddress})
require.NoError(t, err)
require.NotNil(t, contents.GetSend())
require.Equal(t, &spacemeshv2alpha1.ContentsSend{
Destination: to.String(),
Amount: 100,
}, 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) {
Expand Down
16 changes: 3 additions & 13 deletions vm/sdk/multisig/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,9 @@ import (
"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/vm/core"
"github.com/spacemeshos/go-spacemesh/vm/sdk"
"github.com/spacemeshos/go-spacemesh/vm/templates/multisig"
)

// SpawnArguments contains a collection with PublicKeys.
type SpawnArguments struct {
Required uint8
PublicKeys []core.PublicKey
}

type SpendArguments struct {
To types.Address
Amount uint64
}

// part contains a reference to public key and signature from private key counterpart.
type part struct {
Ref uint8
Expand Down Expand Up @@ -72,15 +62,15 @@ func (a *SignatureAggregator) Raw() []byte {
}

func EncodeSpawnArgs(required uint8, pubkeys []core.PublicKey) []byte {
args := SpawnArguments{
args := multisig.SpawnArguments{
Required: required,
PublicKeys: pubkeys,
}
return scale.MustMarshal(args)
}

func EncodeSpendArgs(to types.Address, amount uint64) []byte {
args := SpendArguments{
args := multisig.SpendArguments{
To: to,
Amount: amount,
}
Expand Down
3 changes: 2 additions & 1 deletion vm/sdk/wallet/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/spacemeshos/go-spacemesh/vm/core"
"github.com/spacemeshos/go-spacemesh/vm/host"
"github.com/spacemeshos/go-spacemesh/vm/sdk"
"github.com/spacemeshos/go-spacemesh/vm/templates"
"github.com/spacemeshos/go-spacemesh/vm/templates/wallet"
)

Expand All @@ -27,7 +28,7 @@ func Deploy(pk signing.PrivateKey, nonce core.Nonce, blob []byte, opts ...sdk.Op
}

athPayload := athcon.Payload{
Selector: &wallet.DeploySelector,
Selector: &templates.DeploySelector,
Input: blobEncoded.Bytes(),
}
payload, err := gossamerScale.Marshal(athPayload)
Expand Down
Loading

0 comments on commit 135ca11

Please sign in to comment.