Skip to content

Commit

Permalink
refactor: SpendTx, AENS transaction constructors now do a lot of hard…
Browse files Browse the repository at this point in the history
… work previously done by Context

move GetTTL/GetNextNonce to package transactions
  • Loading branch information
randomshinichi committed Nov 11, 2019
1 parent 2ee99ca commit 40f5481
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 381 deletions.
234 changes: 0 additions & 234 deletions aeternity/helpers.go
Original file line number Diff line number Diff line change
@@ -1,74 +1,17 @@
package aeternity

import (
"crypto/rand"
"fmt"
"math/big"
"strconv"
"strings"
"time"

"github.com/aeternity/aepp-sdk-go/v6/account"
"github.com/aeternity/aepp-sdk-go/v6/binary"
"github.com/aeternity/aepp-sdk-go/v6/config"
"github.com/aeternity/aepp-sdk-go/v6/naet"
"github.com/aeternity/aepp-sdk-go/v6/transactions"
rlp "github.com/randomshinichi/rlpae"
)

// GetTTLFunc defines a function that will return an appropriate TTL for a
// transaction.
type GetTTLFunc func(offset uint64) (ttl uint64, err error)

// GetNextNonceFunc defines a function that will return an unused account nonce
// for making a transaction.
type GetNextNonceFunc func(accountID string) (nonce uint64, err error)

// GetTTLNonceFunc describes a function that combines the roles of GetTTLFunc
// and GetNextNonceFunc
type GetTTLNonceFunc func(address string, offset uint64) (ttl, nonce uint64, err error)

// GenerateGetTTL returns the chain height + offset
func GenerateGetTTL(n naet.GetHeighter) GetTTLFunc {
return func(offset uint64) (ttl uint64, err error) {
height, err := n.GetHeight()
if err != nil {
return
}
ttl = height + offset
return
}
}

// GenerateGetNextNonce retrieves the current accountNonce and adds 1 to it for
// use in transaction building
func GenerateGetNextNonce(n naet.GetAccounter) GetNextNonceFunc {
return func(accountID string) (nextNonce uint64, err error) {
a, err := n.GetAccount(accountID)
if err != nil {
return
}
nextNonce = *a.Nonce + 1
return
}
}

// GenerateGetTTLNonce combines the commonly used together functions of GetTTL
// and GetNextNonce
func GenerateGetTTLNonce(ttlFunc GetTTLFunc, nonceFunc GetNextNonceFunc) GetTTLNonceFunc {
return func(accountID string, offset uint64) (ttl, nonce uint64, err error) {
ttl, err = ttlFunc(offset)
if err != nil {
return
}
nonce, err = nonceFunc(accountID)
if err != nil {
return
}
return
}
}

// GetAnythingByNameFunc describes a function that returns lookup results for a
// AENS name
type GetAnythingByNameFunc func(name, key string) (results []string, err error)
Expand Down Expand Up @@ -147,111 +90,6 @@ func NewContextFromNode(node *naet.Node, address string) (ctx *Context) {
return
}

// SpendTx creates a spend transaction, filling in the account nonce and
// transaction TTL automatically.
func (c *Context) SpendTx(senderID string, recipientID string, amount, fee *big.Int, payload []byte) (tx *transactions.SpendTx, err error) {
txTTL, accountNonce, err := c.GetTTLNonce(c.Address, config.Client.TTL)
if err != nil {
return
}

// create the transaction
return transactions.NewSpendTx(senderID, recipientID, amount, fee, payload, txTTL, accountNonce), err
}

// NamePreclaimTx creates a name preclaim transaction, filling in the account
// nonce and transaction TTL automatically. It also generates a commitment ID
// and salt, later used to claim the name.
func (c *Context) NamePreclaimTx(name string, fee *big.Int) (tx *transactions.NamePreclaimTx, nameSalt *big.Int, err error) {
txTTL, accountNonce, err := c.GetTTLNonce(c.Address, config.Client.TTL)
if err != nil {
return
}

// calculate the commitment and get the preclaim salt since the salt is 32
// bytes long, you must use a big.Int to convert it into an integer
cm, nameSalt, err := generateCommitmentID(name)
if err != nil {
return
}

// build the transaction
tx = transactions.NewNamePreclaimTx(c.Address, cm, fee, txTTL, accountNonce)

return
}

// NameClaimTx creates a claim transaction, filling in the account nonce and
// transaction TTL automatically.
func (c *Context) NameClaimTx(name string, nameSalt, nameFee, fee *big.Int) (tx *transactions.NameClaimTx, err error) {
txTTL, accountNonce, err := c.GetTTLNonce(c.Address, config.Client.TTL)
if err != nil {
return
}

// create the transaction
tx = transactions.NewNameClaimTx(c.Address, name, nameSalt, nameFee, fee, txTTL, accountNonce)

return tx, err
}

// NameUpdateTx creates a name update transaction, filling in the account nonce
// and transaction TTL automatically.
func (c *Context) NameUpdateTx(name string, targetAddress string) (tx *transactions.NameUpdateTx, err error) {
txTTL, accountNonce, err := c.GetTTLNonce(c.Address, config.Client.TTL)
if err != nil {
return
}

nm, err := transactions.NameID(name)
if err != nil {
return
}

absNameTTL, err := c.GetTTL(config.Client.Names.NameTTL)
if err != nil {
return
}
// create the transaction
tx = transactions.NewNameUpdateTx(c.Address, nm, []string{targetAddress}, absNameTTL, config.Client.Names.ClientTTL, config.Client.Fee, txTTL, accountNonce)

return
}

// NameTransferTx creates a name transfer transaction, filling in the account
// nonce and transaction TTL automatically.
func (c *Context) NameTransferTx(name string, recipientAddress string) (tx *transactions.NameTransferTx, err error) {
txTTL, accountNonce, err := c.GetTTLNonce(c.Address, config.Client.TTL)
if err != nil {
return
}

nm, err := transactions.NameID(name)
if err != nil {
return
}

tx = transactions.NewNameTransferTx(c.Address, nm, recipientAddress, config.Client.Fee, txTTL, accountNonce)
return
}

// NameRevokeTx creates a name revoke transaction, filling in the account nonce
// and transaction TTL automatically.
func (c *Context) NameRevokeTx(name string) (tx *transactions.NameRevokeTx, err error) {
txTTL, accountNonce, err := c.GetTTLNonce(c.Address, config.Client.TTL)
if err != nil {
return
}

nm, err := transactions.NameID(name)
if err != nil {
return
}

tx = transactions.NewNameRevokeTx(c.Address, nm, config.Client.Fee, txTTL, accountNonce)
return
}

// OracleRegisterTx creates an oracle register transaction, filling in the
// account nonce and transaction TTL automatically.
func (c *Context) OracleRegisterTx(querySpec, responseSpec string, queryFee *big.Int, oracleTTLType, oracleTTLValue uint64, VMVersion uint16) (tx *transactions.OracleRegisterTx, err error) {
Expand Down Expand Up @@ -433,75 +271,3 @@ func SignBroadcastWaitTransaction(tx rlp.Encoder, signingAccount *account.Accoun
blockHeight, blockHash, err = WaitForTransactionForXBlocks(n, hash, x)
return
}

// generateCommitmentID gives a commitment ID 'cm_...' given a particular AENS
// name. It is split into the deterministic part computeCommitmentID(), which
// can be tested, and the part incorporating random salt generateCommitmentID()
//
// since the salt is a uint256, which Erlang handles well, but Go has nothing
// similar to it, it is imperative that the salt be kept as a bytearray unless
// you really have to convert it into an integer. Which you usually don't,
// because it's a salt.
func generateCommitmentID(name string) (ch string, salt *big.Int, err error) {
// Generate 32 random bytes for a salt
saltBytes := make([]byte, 32)
_, err = rand.Read(saltBytes)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
return
}

ch, err = computeCommitmentID(name, saltBytes)

salt = new(big.Int)
salt.SetBytes(saltBytes)

return ch, salt, err
}

func isPrintable(s string) bool {
for _, r := range s {
if !strconv.IsPrint(r) {
return false
}
}
return true
}

func computeCommitmentID(name string, salt []byte) (ch string, err error) {
var nh = []byte{}
if strings.HasSuffix(name, ".test") {
nh = append(Namehash(name), salt...)

} else {
// Since UTF-8 ~ ASCII, just use the string directly. QuoteToASCII
// includes an extra byte at the start and end of the string, messing up
// the hashing process.
if !isPrintable(name) {
return "", fmt.Errorf("The name %s must contain only printable characters", name)
}

nh = append([]byte(name), salt...)
}
nh, err = binary.Blake2bHash(nh)
if err != nil {
return
}
ch = binary.Encode(binary.PrefixCommitment, nh)
return
}

// Namehash calculate the Namehash of a string. Names within aeternity are
// generally referred to only by their namehashes.
//
// The implementation is the same as ENS EIP-137
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md#namehash-algorithm
// but using Blake2b.
func Namehash(name string) []byte {
buf := make([]byte, 32)
for _, s := range strings.Split(name, ".") {
sh, _ := binary.Blake2bHash([]byte(s))
buf, _ = binary.Blake2bHash(append(buf, sh...))
}
return buf
}
97 changes: 0 additions & 97 deletions aeternity/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package aeternity
import (
"testing"

"github.com/aeternity/aepp-sdk-go/v6/binary"
"github.com/aeternity/aepp-sdk-go/v6/swagguard/node/models"
"github.com/aeternity/aepp-sdk-go/v6/utils"
)
Expand Down Expand Up @@ -50,99 +49,3 @@ func TestWaitForTransactionForXBlocks(t *testing.T) {
t.Fatalf("Expected mock blockHash bh_someblockhash, got %s", blockHash)
}
}
func Test_Namehash(t *testing.T) {
// ('welghmolql.aet') == 'nm_2KrC4asc6fdv82uhXDwfiqB1TY2htjhnzwzJJKLxidyMymJRUQ'
type args struct {
name string
}
tests := []struct {
name string
args args
want string
}{
{"ok", args{"welghmolql.aet"}, "nm_2KrC4asc6fdv82uhXDwfiqB1TY2htjhnzwzJJKLxidyMymJRUQ"},
{"ok", args{"welghmolql"}, "nm_2nLRBu1FyukEvJuMANjFzx8mubMFeyG2mJ2QpQoYKymYe1d2sr"},
{"ok", args{"fdsa.test"}, "nm_ie148R2qZYBfo1Ek3sZpfTLwBhkkqCRKi2Ce8JJ7yyWVRw2Sb"},
{"ok", args{""}, "nm_2q1DrgEuxRNCWRp5nTs6FyA7moSEzrPVUSTEpkpFsM4hRL4Dkb"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := binary.Encode(binary.PrefixName, Namehash(tt.args.name))
if got != tt.want {
t.Errorf("Namehash() = %v, want %v", got, tt.want)
}
})
}
}

func Test_computeCommitmentID(t *testing.T) {
type args struct {
name string
salt []byte
}
tests := []struct {
name string
args args
wantCh string
wantErr bool
}{
{
name: "fdsa.test, 0",
args: args{
name: "fdsa.test",
salt: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
wantCh: "cm_2jJov6dn121oKkHo6TuWaAAL4ZEMonnCjpo8jatkCixrLG8Uc4",
wantErr: false,
},
{
name: "fdsa.test, 255",
args: args{
name: "fdsa.test",
salt: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255},
},
wantCh: "cm_sa8UUjorPzCTLfYp6YftR4jwF4kPaZVsoP5bKVAqRw9zm43EE",
wantErr: false,
},
{
// erlang Eshell: rp(<<9795159241593061970:256>>).
name: "fdsa.test, 9795159241593061970 (do not use Golang to convert salt integers)",
args: args{
name: "fdsa.test",
salt: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 239, 101, 110, 233, 138, 2, 82},
},
wantCh: "cm_QhtcYow8krP3xQSTsAhFihfBstTjQMiApaPCgZuciDHZmMNtZ",
wantErr: false,
},
{
name: "aeternity.chain, 10692426485854419779",
args: args{
name: "aeternity.chain",
salt: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 99, 33, 60, 236, 84, 187, 67},
},
wantCh: "cm_j5Aa3senWdNskwSSHh3M182ucbqrAaSE5DVjejM8fBCgR97kq",
wantErr: false,
},
{
name: "whatever.chain, 4703192432112",
args: args{
name: "whatever.chain",
salt: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 71, 12, 29, 61, 240},
},
wantCh: "cm_2Hd42FoCDfYxcG3MyZkiN9wXiBKfHHzBWycEvrazPYgoEh1ien",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCh, err := computeCommitmentID(tt.args.name, tt.args.salt)
if (err != nil) != tt.wantErr {
t.Errorf("computeCommitmentID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotCh != tt.wantCh {
t.Errorf("computeCommitmentID() = %v, want %v", gotCh, tt.wantCh)
}
})
}
}

0 comments on commit 40f5481

Please sign in to comment.