diff --git a/aeternity/api.go b/aeternity/api.go index 8f510c75..58f10269 100644 --- a/aeternity/api.go +++ b/aeternity/api.go @@ -208,3 +208,20 @@ func getOracleByPubkey(node *apiclient.Node, pubkey string) (oracle *models.Regi oracle = r.Payload return } + +// APIGetOracleQueriesByPubkey get a list of queries made to a particular oracle +func (ae *Ae) APIGetOracleQueriesByPubkey(pubkey string) (oracleQueries *models.OracleQueries, err error) { + return getOracleQueriesByPubkey(ae.Node, pubkey) +} + +// getOracleQueriesByPubkey get a list of queries made to a particular oracle +func getOracleQueriesByPubkey(node *apiclient.Node, pubkey string) (oracleQueries *models.OracleQueries, err error) { + p := external.NewGetOracleQueriesByPubkeyParams().WithPubkey(pubkey) + r, err := node.External.GetOracleQueriesByPubkey(p) + if err != nil { + err = fmt.Errorf("Error: %v", getErrorReason(err)) + return + } + oracleQueries = r.Payload + return +} diff --git a/aeternity/hashing.go b/aeternity/hashing.go index 58a81b9c..6d3691b0 100644 --- a/aeternity/hashing.go +++ b/aeternity/hashing.go @@ -81,6 +81,39 @@ func hash(in []byte) (out []byte, err error) { return } +func leftPadByteSlice(length int, data []byte) []byte { + dataLen := len(data) + t := make([]byte, length-dataLen) + paddedSlice := append(t, data...) + return paddedSlice +} + +func buildOracleQueryID(sender string, senderNonce uint64, recipient string) (id string, err error) { + queryIDBin := []byte{} + senderBin, err := Decode(sender) + if err != nil { + return + } + queryIDBin = append(queryIDBin, senderBin...) + + senderNonceBytes := utils.NewBigIntFromUint64(senderNonce).Bytes() + senderNonceBytesPadded := leftPadByteSlice(32, senderNonceBytes) + queryIDBin = append(queryIDBin, senderNonceBytesPadded...) + + recipientBin, err := Decode(recipient) + if err != nil { + return + } + queryIDBin = append(queryIDBin, recipientBin...) + + hashedQueryID, err := hash(queryIDBin) + if err != nil { + return + } + id = Encode(PrefixOracleQueryID, hashedQueryID) + return +} + // Namehash calculate the Namehash of a string // TODO: link to the func Namehash(name string) []byte { @@ -157,6 +190,7 @@ func buildIDTag(IDTag uint8, encodedHash string) (v []uint8, err error) { return } +// DecodeRLPMessage transforms a plain stream of bytes into a structure of bytes that represents the object that was serialized func DecodeRLPMessage(rawBytes []byte) []interface{} { res := []interface{}{} rlp.DecodeBytes(rawBytes, &res) diff --git a/aeternity/hashing_test.go b/aeternity/hashing_test.go index d1f79d94..c5ea4cb0 100644 --- a/aeternity/hashing_test.go +++ b/aeternity/hashing_test.go @@ -241,3 +241,88 @@ func Test_computeCommitmentID(t *testing.T) { }) } } + +func Test_buildOracleQueryID(t *testing.T) { + type args struct { + sender string + senderNonce uint64 + recipient string + } + tests := []struct { + name string + args args + wantID string + wantErr bool + }{ + { + name: "a simple oracle query id", + args: args{ + sender: "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi", + senderNonce: uint64(3), + recipient: "ok_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi", + }, + wantID: "oq_2NhMjBdKHJYnQjDbAxanmxoXiSiWDoG9bqDgk2MfK2X6AB9Bwx", + wantErr: false, + }, + { + name: "this test case copied from aepp-middleware", + args: args{ + sender: "ak_2ZjpYpJbzq8xbzjgPuEpdq9ahZE7iJRcAYC1weq3xdrNbzRiP4", + senderNonce: uint64(1), + recipient: "ok_2iqfJjbhGgJFRezjX6Q6DrvokkTM5niGEHBEJZ7uAG5fSGJAw1", + }, + wantID: "oq_2YvZnoohcSvbQCsPKSMxc98i5HZ1sU5mR6xwJUZC3SvkuSynMj", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotID, err := buildOracleQueryID(tt.args.sender, tt.args.senderNonce, tt.args.recipient) + if (err != nil) != tt.wantErr { + t.Errorf("buildOracleQueryID() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotID != tt.wantID { + gotIDBytes, _ := Decode(gotID) + wantIDBytes, _ := Decode(tt.wantID) + t.Errorf("buildOracleQueryID() = \n%v\n%v, want \n%v\n%v", gotID, gotIDBytes, tt.wantID, wantIDBytes) + } + }) + } +} + +func Test_leftPadByteSlice(t *testing.T) { + type args struct { + length int + data []byte + } + tests := []struct { + name string + args args + want []byte + }{ + { + name: "Left pad a nonce of 3 to 32 bytes", + args: args{ + length: 32, + data: []byte{3}, + }, + want: []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, 3}, + }, + { + name: "Left pad a multi-byte value to 32 bytes", + args: args{ + length: 32, + data: []byte{1, 2, 3, 4, 3}, + }, + want: []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, 1, 2, 3, 4, 3}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := leftPadByteSlice(tt.args.length, tt.args.data); !reflect.DeepEqual(got, tt.want) { + t.Errorf("leftPadByteSlice() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/aeternity/helpers.go b/aeternity/helpers.go index 1152467d..f3b4b521 100644 --- a/aeternity/helpers.go +++ b/aeternity/helpers.go @@ -26,21 +26,6 @@ func urlComponents(url string) (host string, schemas []string) { return } -// GetTTL returns the chain height + offset -func (ae *Ae) GetTTL(offset uint64) (height uint64, err error) { - return getTTL(ae.Node, offset) -} - -// GetNextNonce retrieves the current accountNonce for an account + 1 -func (ae *Ae) GetNextNonce(accountID string) (nextNonce uint64, err error) { - return getNextNonce(ae.Node, accountID) -} - -// GetTTLNonce is a convenience function that combines GetTTL() and GetNextNonce() -func (ae *Ae) GetTTLNonce(accountID string, offset uint64) (txTTL, accountNonce uint64, err error) { - return getTTLNonce(ae.Node, accountID, offset) -} - func getTTL(node *apiclient.Node, offset uint64) (height uint64, err error) { kb, err := getTopBlock(node) if err != nil { @@ -104,18 +89,6 @@ func waitForTransaction(nodeClient *apiclient.Node, txHash string) (blockHeight return } -// // SpendTxStr creates an unsigned SpendTx but returns the base64 representation instead of an RLP bytestring -// func SpendTxStr(sender, recipient string, amount, fee utils.BigInt, message string, txTTL, accountNonce uint64) (base64Tx string, err error) { -// rlpUnsignedTx, err := NewSpendTx(sender, recipient, amount, fee, message, txTTL, accountNonce) -// if err != nil { -// return -// } - -// base64Tx = Encode(PrefixTransaction, rlpUnsignedTx) - -// return base64Tx, err -// } - // BroadcastTransaction recalculates the transaction hash and sends the transaction to the node. func (ae *Ae) BroadcastTransaction(txSignedBase64 string) (err error) { // Get back to RLP to calculate txhash @@ -131,71 +104,16 @@ func (ae *Ae) BroadcastTransaction(txSignedBase64 string) (err error) { return } -// NamePreclaimTx creates a name preclaim transaction and salt (required for claiming) -// It should return the Tx struct, not the base64 encoded RLP, to ease subsequent inspection. -func (n *Aens) NamePreclaimTx(name string, fee utils.BigInt) (tx NamePreclaimTx, nameSalt *utils.BigInt, err error) { - txTTL, accountNonce, err := getTTLNonce(n.nodeClient, n.owner.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 NamePreclaimTx{}, utils.NewBigInt(), err - } - - // build the transaction - tx = NewNamePreclaimTx(n.owner.Address, cm, fee, txTTL, accountNonce) - if err != nil { - return NamePreclaimTx{}, utils.NewBigInt(), err - } - - return -} - -// NameClaimTx creates a claim transaction -func (n *Aens) NameClaimTx(name string, nameSalt utils.BigInt, fee utils.BigInt) (tx NameClaimTx, err error) { - txTTL, accountNonce, err := getTTLNonce(n.nodeClient, n.owner.Address, Config.Client.TTL) - if err != nil { - return - } - - // create the transaction - tx = NewNameClaimTx(n.owner.Address, name, nameSalt, fee, txTTL, accountNonce) - - return tx, err +// GetTTL returns the chain height + offset +func (ae *Ae) GetTTL(offset uint64) (height uint64, err error) { + return getTTL(ae.Node, offset) } -// NameUpdateTx perform a name update -func (n *Aens) NameUpdateTx(name string, targetAddress string) (tx NameUpdateTx, err error) { - txTTL, accountNonce, err := getTTLNonce(n.nodeClient, n.owner.Address, Config.Client.TTL) - if err != nil { - return - } - - encodedNameHash := Encode(PrefixName, Namehash(name)) - absNameTTL, err := getTTL(n.nodeClient, Config.Client.Names.NameTTL) - if err != nil { - return NameUpdateTx{}, err - } - // create and sign the transaction - tx = NewNameUpdateTx(n.owner.Address, encodedNameHash, []string{targetAddress}, absNameTTL, Config.Client.Names.ClientTTL, Config.Client.Names.UpdateFee, txTTL, accountNonce) - - return +// GetNextNonce retrieves the current accountNonce for an account + 1 +func (ae *Ae) GetNextNonce(accountID string) (nextNonce uint64, err error) { + return getNextNonce(ae.Node, accountID) } -// // OracleRegisterTxStr register an oracle -// func (o *Oracle) OracleRegisterTxStr(accountNonce uint64, querySpec, responseSpec string, queryFee utils.BigInt, oracleTTLType, oracleTTLValue, abiVersion uint64, txFee utils.BigInt, txTTL uint64) (tx string, err error) { -// txRaw, err := OracleRegisterTx(o.owner.Address, accountNonce, querySpec, responseSpec, Config.Client.Oracles.QueryFee, oracleTTLType, oracleTTLValue, Config.Client.Oracles.VMVersion, txFee, txTTL) -// if err != nil { -// return "", err -// } -// tx = Encode(PrefixTransaction, txRaw) -// return -// } - // PrintGenerationByHeight utility function to print a generation by it's height func (ae *Ae) PrintGenerationByHeight(height uint64) { p := external.NewGetGenerationByHeightParams().WithHeight(height) @@ -261,6 +179,7 @@ Main: p := external.NewGetMicroBlockTransactionsByHashParams().WithHash(mbhs) r, mErr := ae.External.GetMicroBlockTransactionsByHash(p) if mErr != nil { + // TODO: err will still be nil outside this scope. Consider refactoring whole function. err = mErr break Main } @@ -293,6 +212,110 @@ Main: return } +// GetTTLNonce is a convenience function that combines GetTTL() and GetNextNonce() +func (ae *Ae) GetTTLNonce(accountID string, offset uint64) (txTTL, accountNonce uint64, err error) { + return getTTLNonce(ae.Node, accountID, offset) +} + +// NamePreclaimTx creates a name preclaim transaction and salt (required for claiming) +// It should return the Tx struct, not the base64 encoded RLP, to ease subsequent inspection. +func (n *Aens) NamePreclaimTx(name string, fee utils.BigInt) (tx NamePreclaimTx, nameSalt *utils.BigInt, err error) { + txTTL, accountNonce, err := getTTLNonce(n.nodeClient, n.owner.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 NamePreclaimTx{}, utils.NewBigInt(), err + } + + // build the transaction + tx = NewNamePreclaimTx(n.owner.Address, cm, fee, txTTL, accountNonce) + if err != nil { + return NamePreclaimTx{}, utils.NewBigInt(), err + } + + return +} + +// NameClaimTx creates a claim transaction +func (n *Aens) NameClaimTx(name string, nameSalt utils.BigInt, fee utils.BigInt) (tx NameClaimTx, err error) { + txTTL, accountNonce, err := getTTLNonce(n.nodeClient, n.owner.Address, Config.Client.TTL) + if err != nil { + return + } + + // create the transaction + tx = NewNameClaimTx(n.owner.Address, name, nameSalt, fee, txTTL, accountNonce) + + return tx, err +} + +// NameUpdateTx perform a name update +func (n *Aens) NameUpdateTx(name string, targetAddress string) (tx NameUpdateTx, err error) { + txTTL, accountNonce, err := getTTLNonce(n.nodeClient, n.owner.Address, Config.Client.TTL) + if err != nil { + return + } + + encodedNameHash := Encode(PrefixName, Namehash(name)) + absNameTTL, err := getTTL(n.nodeClient, Config.Client.Names.NameTTL) + if err != nil { + return NameUpdateTx{}, err + } + // create and sign the transaction + tx = NewNameUpdateTx(n.owner.Address, encodedNameHash, []string{targetAddress}, absNameTTL, Config.Client.Names.ClientTTL, Config.Client.Names.UpdateFee, txTTL, accountNonce) + + return +} + +// OracleRegisterTx create a new oracle +func (o *Oracle) OracleRegisterTx(querySpec, responseSpec string, queryFee utils.BigInt, oracleTTLType, oracleTTLValue, abiVersion uint64, vmVersion uint64) (tx OracleRegisterTx, err error) { + ttl, nonce, err := getTTLNonce(o.nodeClient, o.owner.Address, Config.Client.TTL) + if err != nil { + return OracleRegisterTx{}, err + } + + tx = NewOracleRegisterTx(o.owner.Address, nonce, querySpec, responseSpec, queryFee, oracleTTLType, oracleTTLValue, abiVersion, vmVersion, Config.Client.Fee, ttl) + return tx, nil +} + +// OracleExtendTx extend the lifetime of an existing oracle +func (o *Oracle) OracleExtendTx(oracleID string, ttlType, ttlValue uint64) (tx OracleExtendTx, err error) { + ttl, nonce, err := getTTLNonce(o.nodeClient, o.owner.Address, Config.Client.TTL) + if err != nil { + return OracleExtendTx{}, err + } + + tx = NewOracleExtendTx(oracleID, nonce, ttlType, ttlValue, Config.Client.Fee, ttl) + return tx, nil +} + +// OracleQueryTx ask something of an oracle +func (o *Oracle) OracleQueryTx(OracleID, Query string, QueryFee utils.BigInt, QueryTTLType, QueryTTLValue, ResponseTTLType, ResponseTTLValue uint64) (tx OracleQueryTx, err error) { + ttl, nonce, err := getTTLNonce(o.nodeClient, o.owner.Address, Config.Client.TTL) + if err != nil { + return OracleQueryTx{}, err + } + + tx = NewOracleQueryTx(o.owner.Address, nonce, OracleID, Query, QueryFee, QueryTTLType, QueryTTLValue, ResponseTTLType, ResponseTTLValue, Config.Client.Fee, ttl) + return tx, nil +} + +// OracleRespondTx the oracle responds by sending this transaction +func (o *Oracle) OracleRespondTx(OracleID string, QueryID string, Response string, TTLType uint64, TTLValue uint64) (tx OracleRespondTx, err error) { + ttl, nonce, err := getTTLNonce(o.nodeClient, o.owner.Address, Config.Client.TTL) + if err != nil { + return OracleRespondTx{}, err + } + + tx = NewOracleRespondTx(OracleID, nonce, QueryID, Response, TTLType, TTLValue, Config.Client.Fee, ttl) + return tx, nil +} + // StoreAccountToKeyStoreFile store an account to a json file func StoreAccountToKeyStoreFile(account *Account, password, walletName string) (filePath string, err error) { // keystore will be saved in current directory diff --git a/aeternity/transactions.go b/aeternity/transactions.go index 3fdddb7e..5f981b97 100644 --- a/aeternity/transactions.go +++ b/aeternity/transactions.go @@ -2,8 +2,10 @@ package aeternity import ( "fmt" + "io" "github.com/aeternity/aepp-sdk-go/generated/models" + "github.com/aeternity/aepp-sdk-go/rlp" "github.com/aeternity/aepp-sdk-go/utils" ) @@ -49,22 +51,22 @@ func ttlTypeIntToStr(i uint64) string { return oracleTTLTypeStr } -func buildPointers(pointers []string) (ptrs []*models.NamePointer, err error) { +func buildPointers(pointers []string) (ptrs []*NamePointer, err error) { // TODO: handle errors - ptrs = make([]*models.NamePointer, len(pointers)) + ptrs = make([]*NamePointer, len(pointers)) for i, p := range pointers { switch GetHashPrefix(p) { case PrefixAccountPubkey: // pID, err := buildIDTag(IDTagAccount, p) key := "account_pubkey" - ptrs[i] = &models.NamePointer{ID: models.EncodedHash(p), Key: &key} + ptrs[i] = NewNamePointer(key, p) if err != nil { break } case PrefixOraclePubkey: // pID, err := buildIDTag(IDTagOracle, p) key := "oracle_pubkey" - ptrs[i] = &models.NamePointer{ID: models.EncodedHash(p), Key: &key} + ptrs[i] = NewNamePointer(key, p) if err != nil { break } @@ -81,7 +83,6 @@ func buildPointers(pointers []string) (ptrs []*models.NamePointer, err error) { // See https://tour.golang.org/methods/4 or https://dave.cheney.net/2016/03/19/should-methods-be-declared-on-t-or-t type Tx interface { RLP() ([]byte, error) - JSON() (string, error) } // BaseEncodeTx takes a Tx, runs its RLP() method, and base encodes the result. @@ -157,7 +158,7 @@ type NamePreclaimTx struct { CommitmentID string Fee utils.BigInt TTL uint64 - Nonce uint64 + AccountNonce uint64 } // RLP returns a byte serialized representation @@ -177,7 +178,7 @@ func (t *NamePreclaimTx) RLP() (rlpRawMsg []byte, err error) { ObjectTagNameServicePreclaimTransaction, rlpMessageVersion, aID, - t.Nonce, + t.AccountNonce, cID, t.Fee.Int, t.TTL) @@ -190,7 +191,7 @@ func (t *NamePreclaimTx) JSON() (string, error) { AccountID: models.EncodedHash(t.AccountID), CommitmentID: models.EncodedHash(t.CommitmentID), Fee: t.Fee, - Nonce: t.Nonce, + Nonce: t.AccountNonce, TTL: t.TTL, } output, err := swaggerT.MarshalBinary() @@ -198,20 +199,20 @@ func (t *NamePreclaimTx) JSON() (string, error) { } // NewNamePreclaimTx is a constructor for a NamePreclaimTx struct -func NewNamePreclaimTx(accountID, commitmentID string, fee utils.BigInt, ttl, nonce uint64) NamePreclaimTx { - return NamePreclaimTx{accountID, commitmentID, fee, ttl, nonce} +func NewNamePreclaimTx(accountID, commitmentID string, fee utils.BigInt, ttl, accountNonce uint64) NamePreclaimTx { + return NamePreclaimTx{accountID, commitmentID, fee, ttl, accountNonce} } // NameClaimTx represents a transaction where one claims a previously reserved name on AENS // The revealed name is simply sent in plaintext in RLP, while in JSON representation // it is base58 encoded. type NameClaimTx struct { - AccountID string - Name string - NameSalt utils.BigInt - Fee utils.BigInt - TTL uint64 - Nonce uint64 + AccountID string + Name string + NameSalt utils.BigInt + Fee utils.BigInt + TTL uint64 + AccountNonce uint64 } // RLP returns a byte serialized representation @@ -227,7 +228,7 @@ func (t *NameClaimTx) RLP() (rlpRawMsg []byte, err error) { ObjectTagNameServiceClaimTransaction, rlpMessageVersion, aID, - t.Nonce, + t.AccountNonce, t.Name, t.NameSalt.Int, t.Fee.Int, @@ -245,7 +246,7 @@ func (t *NameClaimTx) JSON() (string, error) { Fee: t.Fee, Name: &nameAPIEncoded, NameSalt: t.NameSalt, - Nonce: t.Nonce, + Nonce: t.AccountNonce, TTL: t.TTL, } @@ -254,21 +255,48 @@ func (t *NameClaimTx) JSON() (string, error) { } // NewNameClaimTx is a constructor for a NameClaimTx struct -func NewNameClaimTx(accountID, name string, nameSalt utils.BigInt, fee utils.BigInt, ttl, nonce uint64) NameClaimTx { - return NameClaimTx{accountID, name, nameSalt, fee, ttl, nonce} +func NewNameClaimTx(accountID, name string, nameSalt utils.BigInt, fee utils.BigInt, ttl, accountNonce uint64) NameClaimTx { + return NameClaimTx{accountID, name, nameSalt, fee, ttl, accountNonce} } -// NameUpdateTx represents a transaction where one extends the lifetime of a reserved name on AENS +// NamePointer extends the swagger gener ated models.NamePointer to provide RLP serialization +type NamePointer struct { + *models.NamePointer +} +// EncodeRLP implements rlp.Encoder interface. +func (t *NamePointer) EncodeRLP(w io.Writer) (err error) { + accountID, err := buildIDTag(IDTagAccount, string(t.NamePointer.ID)) + if err != nil { + return + } + + err = rlp.Encode(w, []interface{}{t.Key, accountID}) + if err != nil { + return + } + return err +} + +// NewNamePointer is a constructor for a swagger generated NamePointer struct. +// It returns a pointer because +func NewNamePointer(key string, id string) *NamePointer { + np := models.NamePointer{ID: models.EncodedHash(id), Key: &key} + return &NamePointer{ + NamePointer: &np, + } +} + +// NameUpdateTx represents a transaction where one extends the lifetime of a reserved name on AENS type NameUpdateTx struct { - AccountID string - NameID string - Pointers []*models.NamePointer - NameTTL uint64 - ClientTTL uint64 - Fee utils.BigInt - TTL uint64 - Nonce uint64 + AccountID string + NameID string + Pointers []*NamePointer + NameTTL uint64 + ClientTTL uint64 + Fee utils.BigInt + TTL uint64 + AccountNonce uint64 } // RLP returns a byte serialized representation @@ -284,15 +312,25 @@ func (t *NameUpdateTx) RLP() (rlpRawMsg []byte, err error) { return } + // reverse the NamePointer order as compared to the JSON serialization, because the node seems to want it that way + i := 0 + j := len(t.Pointers) - 1 + reversedPointers := make([]*NamePointer, len(t.Pointers)) + for i <= len(t.Pointers)-1 { + reversedPointers[i] = t.Pointers[j] + i++ + j-- + } + // create the transaction rlpRawMsg, err = buildRLPMessage( ObjectTagNameServiceUpdateTransaction, rlpMessageVersion, aID, - t.Nonce, + t.AccountNonce, nID, t.NameTTL, - t.Pointers, + reversedPointers, t.ClientTTL, t.Fee.Int, t.TTL) @@ -301,14 +339,19 @@ func (t *NameUpdateTx) RLP() (rlpRawMsg []byte, err error) { // JSON representation of a Tx is useful for querying the node's debug endpoint func (t *NameUpdateTx) JSON() (string, error) { + swaggerNamePointers := []*models.NamePointer{} + for _, np := range t.Pointers { + swaggerNamePointers = append(swaggerNamePointers, np.NamePointer) + } + swaggerT := models.NameUpdateTx{ AccountID: models.EncodedHash(t.AccountID), ClientTTL: &t.ClientTTL, Fee: t.Fee, NameID: models.EncodedHash(t.NameID), NameTTL: &t.NameTTL, - Nonce: t.Nonce, - Pointers: t.Pointers, + Nonce: t.AccountNonce, + Pointers: swaggerNamePointers, TTL: t.TTL, } @@ -317,12 +360,12 @@ func (t *NameUpdateTx) JSON() (string, error) { } // NewNameUpdateTx is a constructor for a NameUpdateTx struct -func NewNameUpdateTx(accountID, nameID string, pointers []string, nameTTL, clientTTL uint64, fee utils.BigInt, ttl, nonce uint64) NameUpdateTx { +func NewNameUpdateTx(accountID, nameID string, pointers []string, nameTTL, clientTTL uint64, fee utils.BigInt, ttl, accountNonce uint64) NameUpdateTx { parsedPointers, err := buildPointers(pointers) if err != nil { panic(err) } - return NameUpdateTx{accountID, nameID, parsedPointers, nameTTL, clientTTL, fee, ttl, nonce} + return NameUpdateTx{accountID, nameID, parsedPointers, nameTTL, clientTTL, fee, ttl, accountNonce} } // OracleRegisterTx represents a transaction that registers an oracle on the blockchain's state @@ -413,17 +456,17 @@ func NewOracleRegisterTx(accountID string, accountNonce uint64, querySpec, respo // OracleExtendTx represents a transaction that extends the lifetime of an oracle type OracleExtendTx struct { - OracleID string - AccountNonce uint64 - TTLType uint64 - TTLValue uint64 - Fee utils.BigInt - TTL uint64 + OracleID string + AccountNonce uint64 + OracleTTLType uint64 + OracleTTLValue uint64 + TxFee utils.BigInt + TxTTL uint64 } // RLP returns a byte serialized representation func (t *OracleExtendTx) RLP() (rlpRawMsg []byte, err error) { - aID, err := buildIDTag(IDTagOracle, t.OracleID) + oID, err := buildIDTag(IDTagOracle, t.OracleID) if err != nil { return } @@ -431,28 +474,28 @@ func (t *OracleExtendTx) RLP() (rlpRawMsg []byte, err error) { rlpRawMsg, err = buildRLPMessage( ObjectTagOracleExtendTransaction, rlpMessageVersion, - aID, + oID, t.AccountNonce, - t.TTLType, - t.TTLValue, - t.Fee.Int, - t.TTL) + t.OracleTTLType, + t.OracleTTLValue, + t.TxFee.Int, + t.TxTTL) return } // JSON representation of a Tx is useful for querying the node's debug endpoint func (t *OracleExtendTx) JSON() (string, error) { - oracleTTLTypeStr := ttlTypeIntToStr(t.TTLType) + oracleTTLTypeStr := ttlTypeIntToStr(t.OracleTTLType) swaggerT := models.OracleExtendTx{ - Fee: t.Fee, + Fee: t.TxFee, Nonce: t.AccountNonce, OracleID: models.EncodedHash(t.OracleID), OracleTTL: &models.RelativeTTL{ Type: &oracleTTLTypeStr, - Value: &t.TTLValue, + Value: &t.OracleTTLValue, }, - TTL: t.TTL, + TTL: t.TxTTL, } output, err := swaggerT.MarshalBinary() @@ -460,8 +503,8 @@ func (t *OracleExtendTx) JSON() (string, error) { } // NewOracleExtendTx is a constructor for a OracleExtendTx struct -func NewOracleExtendTx(oracleID string, accountNonce, ttlType, ttlValue uint64, fee utils.BigInt, ttl uint64) OracleExtendTx { - return OracleExtendTx{oracleID, accountNonce, ttlType, ttlValue, fee, ttl} +func NewOracleExtendTx(oracleID string, accountNonce, oracleTTLType, oracleTTLValue uint64, TxFee utils.BigInt, TxTTL uint64) OracleExtendTx { + return OracleExtendTx{oracleID, accountNonce, oracleTTLType, oracleTTLValue, TxFee, TxTTL} } // OracleQueryTx represents a transaction that a program sends to query an oracle @@ -508,6 +551,7 @@ func (t *OracleQueryTx) RLP() (rlpRawMsg []byte, err error) { return } +// JSON representation of a Tx is useful for querying the node's debug endpoint func (t *OracleQueryTx) JSON() (string, error) { responseTTLTypeStr := ttlTypeIntToStr(t.ResponseTTLType) queryTTLTypeStr := ttlTypeIntToStr(t.QueryTTLType) @@ -551,6 +595,52 @@ type OracleRespondTx struct { TxTTL uint64 } +// RLP returns a byte serialized representation +func (t *OracleRespondTx) RLP() (rlpRawMsg []byte, err error) { + oID, err := buildIDTag(IDTagOracle, t.OracleID) + if err != nil { + return + } + queryIDBytes, err := Decode(t.QueryID) + if err != nil { + return + } + + rlpRawMsg, err = buildRLPMessage( + ObjectTagOracleResponseTransaction, + rlpMessageVersion, + oID, + t.AccountNonce, + queryIDBytes, + t.Response, + t.ResponseTTLType, + t.ResponseTTLValue, + t.TxFee.Int, + t.TxTTL) + return +} + +// JSON representation of a Tx is useful for querying the node's debug endpoint +func (t *OracleRespondTx) JSON() (string, error) { + responseTTLTypeStr := ttlTypeIntToStr(t.ResponseTTLType) + + swaggerT := models.OracleRespondTx{ + Fee: t.TxFee, + Nonce: t.AccountNonce, + OracleID: models.EncodedHash(t.OracleID), + QueryID: models.EncodedHash(t.QueryID), + Response: &t.Response, + ResponseTTL: &models.RelativeTTL{ + Type: &responseTTLTypeStr, + Value: &t.ResponseTTLValue, + }, + TTL: t.TxTTL, + } + output, err := swaggerT.MarshalBinary() + return string(output), err + +} + // NewOracleRespondTx is a constructor for a OracleRespondTx struct func NewOracleRespondTx(OracleID string, AccountNonce uint64, QueryID string, Response string, TTLType uint64, TTLValue uint64, TxFee utils.BigInt, TxTTL uint64) OracleRespondTx { return OracleRespondTx{OracleID, AccountNonce, QueryID, Response, TTLType, TTLValue, TxFee, TxTTL} diff --git a/aeternity/transactions_test.go b/aeternity/transactions_test.go index 09959027..002a63e5 100644 --- a/aeternity/transactions_test.go +++ b/aeternity/transactions_test.go @@ -1,6 +1,7 @@ package aeternity import ( + "bytes" "fmt" "reflect" "testing" @@ -86,6 +87,44 @@ func TestSpendTx_RLP(t *testing.T) { }) } } +func TestNamePointer_EncodeRLP(t *testing.T) { + type fields struct { + ID string + Key string + } + tests := []struct { + name string + fields fields + wantW []byte + wantErr bool + }{ + { + name: "1 pointer to a normal ak_ account", + fields: fields{ + Key: "account_pubkey", + ID: "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v", + }, + // the reference value of wantW is taken from a correct serialization of NameUpdateTx. + // Unfortunately there is no way to get the node to serialize just the NamePointer. + wantW: []byte{241, 142, 97, 99, 99, 111, 117, 110, 116, 95, 112, 117, 98, 107, 101, 121, 161, 1, 31, 19, 163, 176, 139, 240, 1, 64, 6, 98, 166, 139, 105, 216, 117, 247, 128, 60, 236, 76, 8, 100, 127, 110, 213, 216, 76, 120, 151, 189, 80, 163}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewNamePointer(tt.fields.Key, tt.fields.ID) + w := &bytes.Buffer{} + if err := p.EncodeRLP(w); (err != nil) != tt.wantErr { + t.Errorf("NamePointer.EncodeRLP() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotW := w.Bytes(); !bytes.Equal(gotW, tt.wantW) { + t.Errorf("NamePointer.EncodeRLP() = %v, want %v", gotW, tt.wantW) + fmt.Println(DecodeRLPMessage(gotW)) + } + }) + } +} func TestNamePreclaimTx_RLP(t *testing.T) { type fields struct { @@ -260,6 +299,7 @@ func TestNameUpdateTx_RLP(t *testing.T) { fmt.Println(txJSON) gotTx, err := BaseEncodeTx(&tx) + if (err != nil) != tt.wantErr { t.Errorf("NameUpdateTx.RLP() error = %v, wantErr %v", err, tt.wantErr) return @@ -437,7 +477,7 @@ func TestOracleExtendTx_RLP(t *testing.T) { } } -func OracleQueryTxRLP(t *testing.T) { +func TestOracleQueryTx_RLP(t *testing.T) { type fields struct { SenderID string AccountNonce uint64 @@ -472,8 +512,8 @@ func OracleQueryTxRLP(t *testing.T) { TxFee: Config.Client.Fee, TxTTL: Config.Client.TTL, }, - // from aepp-sdk-js - wantTx: "tx_+GgXAaEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMBoQTOp63kcMn5nZ1OQAiAqG8dSbtES2LxGp67ZLvP63P+841BcmUgeW91IG9rYXk/AACCASwAggEshrXmIPSAAILEzIsypOc=", + // from the node + wantTx: "tx_+GgXAaEBHxOjsIvwAUAGYqaLadh194A87EwIZH9u1dhMeJe9UKMBoQTOp63kcMn5nZ1OQAiAqG8dSbtES2LxGp67ZLvP63P+841BcmUgeW91IG9rYXk/AACCASwAggEshrXmIPSAAIIB9GPfFkA=", wantErr: false, }, } @@ -507,3 +547,64 @@ func OracleQueryTxRLP(t *testing.T) { }) } } + +func TestOracleRespondTx_RLP(t *testing.T) { + type fields struct { + OracleID string + AccountNonce uint64 + QueryID string + Response string + ResponseTTLType uint64 + ResponseTTLValue uint64 + TxFee utils.BigInt + TxTTL uint64 + } + tests := []struct { + name string + fields fields + wantTx string + wantErr bool + }{ + { + name: "A normal oracle response", + fields: fields{ + OracleID: "ok_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi", + AccountNonce: uint64(1), + QueryID: "oq_2NhMjBdKHJYnQjDbAxanmxoXiSiWDoG9bqDgk2MfK2X6AB9Bwx", + Response: "Hello back", + ResponseTTLType: 0, + ResponseTTLValue: 100, + TxFee: Config.Client.Fee, + TxTTL: Config.Client.TTL, + }, + wantTx: "tx_+F0YAaEEzqet5HDJ+Z2dTkAIgKhvHUm7REti8Rqeu2S7z+tz/vMBoLT1h6fjQDFn1a7j+6wVQ886V47xiFwvkbL+x2yR3J9cikhlbGxvIGJhY2sAZIa15iD0gACCAfQC7+L+", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tx := NewOracleRespondTx( + tt.fields.OracleID, + tt.fields.AccountNonce, + tt.fields.QueryID, + tt.fields.Response, + tt.fields.ResponseTTLType, + tt.fields.ResponseTTLValue, + tt.fields.TxFee, + tt.fields.TxTTL, + ) + gotTx, err := BaseEncodeTx(&tx) + + txJSON, _ := tx.JSON() + fmt.Println(txJSON) + if (err != nil) != tt.wantErr { + t.Errorf("OracleRespondTx.RLP() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotTx, tt.wantTx) { + gotTxRawBytes, wantTxRawBytes := getRLPSerialized(gotTx, tt.wantTx) + t.Errorf("OracleRespondTx.RLP() = \n%v\n%v, want \n%v\n%v", gotTx, gotTxRawBytes, tt.wantTx, wantTxRawBytes) + } + }) + } +} diff --git a/api/edit.py b/api/edit.py index b2d43bfe..1d059a5c 100644 --- a/api/edit.py +++ b/api/edit.py @@ -1,6 +1,8 @@ """ +The node's swagger.json cannot be used out of the box. 1. Manually replace all int64s with uint64s in swagger.json except for time (because go's time.Unix() accepts int64, not uint64) -2. Run this to add Fee/Balance/Amount BigInt in definitions and replace where necessary +2. Run this to add Fee/Balance/Amount/NameSalt BigInt in definitions and replace where necessary +3. generic_tx.go unmarshalGenericTx(): case "ChannelCloseMutualTxJSON": add "ChannelCloseMutualTx" etc for other Tx types CAVEATS /generations/height/{height} cannot be targeted, have to replace by hand diff --git a/build/docker/accounts.json b/build/docker/accounts.json new file mode 100644 index 00000000..89890e6a --- /dev/null +++ b/build/docker/accounts.json @@ -0,0 +1,3 @@ +{ + "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi": 1600000000000000000000000000000000000000 +} \ No newline at end of file diff --git a/cmd/account.go b/cmd/account.go index f61be0ca..a1263a1a 100644 --- a/cmd/account.go +++ b/cmd/account.go @@ -35,6 +35,7 @@ var ( fee string // leave it as a string because viper cannot parse it directly into a BigInt ttl uint64 nonce uint64 + regex bool ) // accountCmd implements the account command @@ -78,7 +79,9 @@ func addressFunc(cmd *cobra.Command, args []string) error { aeternity.Pp("Account address", account.Address) if printPrivateKey { - aeternity.Pp("Account private key", account.SigningKeyToHexString()) + if utils.AskYes("Are you sure you want to print your private key? This could be insecure.", false) { + aeternity.Pp("Account private key", account.SigningKeyToHexString()) + } } return nil @@ -207,37 +210,43 @@ func saveFunc(cmd *cobra.Command, args []string) (err error) { var vanityCmd = &cobra.Command{ Use: "vanity", - Short: "find the account you like", + Short: "Find an account that starts with or contains the user-specified text", Long: ``, Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: vanityFunc, +} - prefix := fmt.Sprintf("^%s", args[0]) - r, err := regexp.Compile(prefix) - if err != nil { - fmt.Println("Ouch! The search input ", prefix, "is not a valid regexp") - return - } - fmt.Println("The search for your account matching", prefix, "has begun") - - var wg sync.WaitGroup - wg.Add(runtime.NumCPU()) - for i := 0; i < runtime.NumCPU(); i++ { - go func() { - for { - a, _ := aeternity.NewAccount() - - if r.MatchString(a.Address[3:]) { - fmt.Println("FOUND!") - fmt.Println("Secret: ", a.SigningKeyToHexString()) - fmt.Println("Address", a.Address) - } +func vanityFunc(cmd *cobra.Command, args []string) { + var searchString string + if regex { + searchString = args[0] + } else { + searchString = fmt.Sprintf("^%s", args[0]) + } + r, err := regexp.Compile(searchString) + if err != nil { + fmt.Println("Ouch! The search input ", searchString, "is not a valid regexp") + return + } + fmt.Println("The search for your account matching", searchString, "has begun") + + var wg sync.WaitGroup + wg.Add(runtime.NumCPU()) + for i := 0; i < runtime.NumCPU(); i++ { + go func() { + for { + a, _ := aeternity.NewAccount() + + if r.MatchString(a.Address[3:]) { + fmt.Println("FOUND!") + fmt.Println("Secret: ", a.SigningKeyToHexString()) + fmt.Println("Address", a.Address) } - }() - } - wg.Wait() + } + }() + } + wg.Wait() - }, } func init() { @@ -251,4 +260,5 @@ func init() { accountCmd.PersistentFlags().StringVar(&password, "password", "", "Read account password from stdin [WARN: this method is not secure]") // account address flags addressCmd.Flags().BoolVar(&printPrivateKey, "private-key", false, "Print the private key as hex string") + vanityCmd.Flags().BoolVar(®ex, "regex", false, "Search using a regular expression that can match anywhere within the address instead of a string that matches at the beginning") } diff --git a/docker-compose.yml b/docker-compose.yml index 13ed4719..d5c32972 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ services: command: -aecore expected_mine_rate ${EPOCH_MINE_RATE:-15000} volumes: - ${PWD}/build/docker/aeternity_node_mean16.yaml:/home/aeternity/aeternity.yaml + - ${PWD}/build/docker/accounts.json:/home/aeternity/node/data/aecore/.genesis/accounts_test.json - ${PWD}/build/docker/keys/node:/home/aeternity/node/keys - node_db:/home/aeternity/node/data/mnesia - node_keys:/home/aeternity/node/keys diff --git a/generated/models/channel_close_mutual_tx_json.go b/generated/models/channel_close_mutual_tx_json.go index 04963343..7c37072f 100644 --- a/generated/models/channel_close_mutual_tx_json.go +++ b/generated/models/channel_close_mutual_tx_json.go @@ -26,7 +26,7 @@ type ChannelCloseMutualTxJSON struct { // Type gets the type of this subtype func (m *ChannelCloseMutualTxJSON) Type() string { - return "ChannelCloseMutualTxJSON" + return "ChannelCloseMutualTx" } // SetType sets the type of this subtype diff --git a/generated/models/channel_close_solo_tx_json.go b/generated/models/channel_close_solo_tx_json.go index 599806b4..61fefb4e 100644 --- a/generated/models/channel_close_solo_tx_json.go +++ b/generated/models/channel_close_solo_tx_json.go @@ -26,7 +26,7 @@ type ChannelCloseSoloTxJSON struct { // Type gets the type of this subtype func (m *ChannelCloseSoloTxJSON) Type() string { - return "ChannelCloseSoloTxJSON" + return "ChannelCloseSoloTx" } // SetType sets the type of this subtype diff --git a/generated/models/channel_create_tx_json.go b/generated/models/channel_create_tx_json.go index ef02ee15..3330d539 100644 --- a/generated/models/channel_create_tx_json.go +++ b/generated/models/channel_create_tx_json.go @@ -26,7 +26,7 @@ type ChannelCreateTxJSON struct { // Type gets the type of this subtype func (m *ChannelCreateTxJSON) Type() string { - return "ChannelCreateTxJSON" + return "ChannelCreateTx" } // SetType sets the type of this subtype diff --git a/generated/models/channel_deposit_tx_json.go b/generated/models/channel_deposit_tx_json.go index 0f0b70cb..90db0ccb 100644 --- a/generated/models/channel_deposit_tx_json.go +++ b/generated/models/channel_deposit_tx_json.go @@ -26,7 +26,7 @@ type ChannelDepositTxJSON struct { // Type gets the type of this subtype func (m *ChannelDepositTxJSON) Type() string { - return "ChannelDepositTxJSON" + return "ChannelDepositTx" } // SetType sets the type of this subtype diff --git a/generated/models/channel_force_progress_tx_json.go b/generated/models/channel_force_progress_tx_json.go index 31b41196..1687037d 100644 --- a/generated/models/channel_force_progress_tx_json.go +++ b/generated/models/channel_force_progress_tx_json.go @@ -26,7 +26,7 @@ type ChannelForceProgressTxJSON struct { // Type gets the type of this subtype func (m *ChannelForceProgressTxJSON) Type() string { - return "ChannelForceProgressTxJSON" + return "ChannelForceProgressTx" } // SetType sets the type of this subtype diff --git a/generated/models/channel_settle_tx_json.go b/generated/models/channel_settle_tx_json.go index 8be1088c..9dceaac0 100644 --- a/generated/models/channel_settle_tx_json.go +++ b/generated/models/channel_settle_tx_json.go @@ -26,7 +26,7 @@ type ChannelSettleTxJSON struct { // Type gets the type of this subtype func (m *ChannelSettleTxJSON) Type() string { - return "ChannelSettleTxJSON" + return "ChannelSettleTx" } // SetType sets the type of this subtype diff --git a/generated/models/channel_sla_s_h_tx_json.go b/generated/models/channel_sla_s_h_tx_json.go index d02570d3..60dbe244 100644 --- a/generated/models/channel_sla_s_h_tx_json.go +++ b/generated/models/channel_sla_s_h_tx_json.go @@ -26,7 +26,7 @@ type ChannelSLASHTxJSON struct { // Type gets the type of this subtype func (m *ChannelSLASHTxJSON) Type() string { - return "ChannelSlashTxJSON" + return "ChannelSlashTx" } // SetType sets the type of this subtype diff --git a/generated/models/channel_snapshot_solo_tx_json.go b/generated/models/channel_snapshot_solo_tx_json.go index 035f1f51..c7389d93 100644 --- a/generated/models/channel_snapshot_solo_tx_json.go +++ b/generated/models/channel_snapshot_solo_tx_json.go @@ -26,7 +26,7 @@ type ChannelSnapshotSoloTxJSON struct { // Type gets the type of this subtype func (m *ChannelSnapshotSoloTxJSON) Type() string { - return "ChannelSnapshotSoloTxJSON" + return "ChannelSnapshotSoloTx" } // SetType sets the type of this subtype diff --git a/generated/models/channel_withdrawal_tx_json.go b/generated/models/channel_withdrawal_tx_json.go index 6e9aa9a2..af36d6a1 100644 --- a/generated/models/channel_withdrawal_tx_json.go +++ b/generated/models/channel_withdrawal_tx_json.go @@ -26,7 +26,7 @@ type ChannelWithdrawalTxJSON struct { // Type gets the type of this subtype func (m *ChannelWithdrawalTxJSON) Type() string { - return "ChannelWithdrawalTxJSON" + return "ChannelWithdrawalTx" } // SetType sets the type of this subtype diff --git a/generated/models/contract_call_tx_json.go b/generated/models/contract_call_tx_json.go index 7f1c1760..0261d0a9 100644 --- a/generated/models/contract_call_tx_json.go +++ b/generated/models/contract_call_tx_json.go @@ -26,7 +26,7 @@ type ContractCallTxJSON struct { // Type gets the type of this subtype func (m *ContractCallTxJSON) Type() string { - return "ContractCallTxJSON" + return "ContractCallTx" } // SetType sets the type of this subtype diff --git a/generated/models/contract_create_tx_json.go b/generated/models/contract_create_tx_json.go index c1330b66..3001b65b 100644 --- a/generated/models/contract_create_tx_json.go +++ b/generated/models/contract_create_tx_json.go @@ -26,7 +26,7 @@ type ContractCreateTxJSON struct { // Type gets the type of this subtype func (m *ContractCreateTxJSON) Type() string { - return "ContractCreateTxJSON" + return "ContractCreateTx" } // SetType sets the type of this subtype diff --git a/generated/models/error.go b/generated/models/error.go index 1ca4fc4b..589fc2e6 100644 --- a/generated/models/error.go +++ b/generated/models/error.go @@ -62,3 +62,7 @@ func (m *Error) UnmarshalBinary(b []byte) error { *m = res return nil } + +func (m *Error) Error() string { + return *m.Reason +} diff --git a/generated/models/generic_tx.go b/generated/models/generic_tx.go index a8282fe0..e919f9d0 100644 --- a/generated/models/generic_tx.go +++ b/generated/models/generic_tx.go @@ -106,77 +106,77 @@ func unmarshalGenericTx(data []byte, consumer runtime.Consumer) (GenericTx, erro // The value of type is used to determine which type to create and unmarshal the data into switch getType.Type { - case "ChannelCloseMutualTxJSON": + case "ChannelCloseMutualTxJSON", "ChannelCloseMutualTx": var result ChannelCloseMutualTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ChannelCloseSoloTxJSON": + case "ChannelCloseSoloTxJSON", "ChannelCloseSoloTx": var result ChannelCloseSoloTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ChannelCreateTxJSON": + case "ChannelCreateTxJSON", "ChannelCreateTx": var result ChannelCreateTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ChannelDepositTxJSON": + case "ChannelDepositTxJSON", "ChannelDepositTx": var result ChannelDepositTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ChannelForceProgressTxJSON": + case "ChannelForceProgressTxJSON", "ChannelForceProgressTx": var result ChannelForceProgressTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ChannelSettleTxJSON": + case "ChannelSettleTxJSON", "ChannelSettleTx": var result ChannelSettleTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ChannelSlashTxJSON": + case "ChannelSlashTxJSON", "ChannelSlashTx": var result ChannelSLASHTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ChannelSnapshotSoloTxJSON": + case "ChannelSnapshotSoloTxJSON", "ChannelSnapshotSoloTx": var result ChannelSnapshotSoloTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ChannelWithdrawalTxJSON": + case "ChannelWithdrawalTxJSON", "ChannelWithdrawalTx": var result ChannelWithdrawalTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ContractCallTxJSON": + case "ContractCallTxJSON", "ContractCallTx": var result ContractCallTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "ContractCreateTxJSON": + case "ContractCreateTxJSON", "ContractCreateTx": var result ContractCreateTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err @@ -190,70 +190,70 @@ func unmarshalGenericTx(data []byte, consumer runtime.Consumer) (GenericTx, erro } return &result, nil - case "NameClaimTxJSON": + case "NameClaimTxJSON", "NameClaimTx": var result NameClaimTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "NamePreclaimTxJSON": + case "NamePreclaimTxJSON", "NamePreclaimTx": var result NamePreclaimTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "NameRevokeTxJSON": + case "NameRevokeTxJSON", "NameRevokeTx": var result NameRevokeTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "NameTransferTxJSON": + case "NameTransferTxJSON", "NameTransferTx": var result NameTransferTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "NameUpdateTxJSON": + case "NameUpdateTxJSON", "NameUpdateTx": var result NameUpdateTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "OracleExtendTxJSON": + case "OracleExtendTxJSON", "OracleExtendTx": var result OracleExtendTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "OracleQueryTxJSON": + case "OracleQueryTxJSON", "OracleQueryTx": var result OracleQueryTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "OracleRegisterTxJSON": + case "OracleRegisterTxJSON", "OracleRegisterTx": var result OracleRegisterTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "OracleResponseTxJSON": + case "OracleResponseTxJSON", "OracleResponseTx": var result OracleResponseTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil - case "SpendTxJSON": + case "SpendTxJSON", "SpendTx": var result SpendTxJSON if err := consumer.Consume(buf2, &result); err != nil { return nil, err diff --git a/generated/models/name_claim_tx_json.go b/generated/models/name_claim_tx_json.go index 6efbf2f3..db7d747d 100644 --- a/generated/models/name_claim_tx_json.go +++ b/generated/models/name_claim_tx_json.go @@ -26,7 +26,7 @@ type NameClaimTxJSON struct { // Type gets the type of this subtype func (m *NameClaimTxJSON) Type() string { - return "NameClaimTxJSON" + return "NameClaimTx" } // SetType sets the type of this subtype diff --git a/generated/models/name_preclaim_tx_json.go b/generated/models/name_preclaim_tx_json.go index 9e7fa92a..c5c4cca9 100644 --- a/generated/models/name_preclaim_tx_json.go +++ b/generated/models/name_preclaim_tx_json.go @@ -26,7 +26,7 @@ type NamePreclaimTxJSON struct { // Type gets the type of this subtype func (m *NamePreclaimTxJSON) Type() string { - return "NamePreclaimTxJSON" + return "NamePreclaimTx" } // SetType sets the type of this subtype diff --git a/generated/models/name_revoke_tx_json.go b/generated/models/name_revoke_tx_json.go index 3cb42282..76cff7cc 100644 --- a/generated/models/name_revoke_tx_json.go +++ b/generated/models/name_revoke_tx_json.go @@ -26,7 +26,7 @@ type NameRevokeTxJSON struct { // Type gets the type of this subtype func (m *NameRevokeTxJSON) Type() string { - return "NameRevokeTxJSON" + return "NameRevokeTx" } // SetType sets the type of this subtype diff --git a/generated/models/name_transfer_tx_json.go b/generated/models/name_transfer_tx_json.go index a7278ac0..a0326b8c 100644 --- a/generated/models/name_transfer_tx_json.go +++ b/generated/models/name_transfer_tx_json.go @@ -26,7 +26,7 @@ type NameTransferTxJSON struct { // Type gets the type of this subtype func (m *NameTransferTxJSON) Type() string { - return "NameTransferTxJSON" + return "NameTransferTx" } // SetType sets the type of this subtype diff --git a/generated/models/name_update_tx_json.go b/generated/models/name_update_tx_json.go index 87b17bb4..22ea3f65 100644 --- a/generated/models/name_update_tx_json.go +++ b/generated/models/name_update_tx_json.go @@ -26,7 +26,7 @@ type NameUpdateTxJSON struct { // Type gets the type of this subtype func (m *NameUpdateTxJSON) Type() string { - return "NameUpdateTxJSON" + return "NameUpdateTx" } // SetType sets the type of this subtype diff --git a/generated/models/oracle_extend_tx_json.go b/generated/models/oracle_extend_tx_json.go index fad1de02..c60fd679 100644 --- a/generated/models/oracle_extend_tx_json.go +++ b/generated/models/oracle_extend_tx_json.go @@ -26,7 +26,7 @@ type OracleExtendTxJSON struct { // Type gets the type of this subtype func (m *OracleExtendTxJSON) Type() string { - return "OracleExtendTxJSON" + return "OracleExtendTx" } // SetType sets the type of this subtype diff --git a/generated/models/oracle_query_tx_json.go b/generated/models/oracle_query_tx_json.go index 213bbee3..39038e06 100644 --- a/generated/models/oracle_query_tx_json.go +++ b/generated/models/oracle_query_tx_json.go @@ -26,7 +26,7 @@ type OracleQueryTxJSON struct { // Type gets the type of this subtype func (m *OracleQueryTxJSON) Type() string { - return "OracleQueryTxJSON" + return "OracleQueryTx" } // SetType sets the type of this subtype diff --git a/generated/models/oracle_register_tx_json.go b/generated/models/oracle_register_tx_json.go index 7a013a3e..f6fa401f 100644 --- a/generated/models/oracle_register_tx_json.go +++ b/generated/models/oracle_register_tx_json.go @@ -26,7 +26,7 @@ type OracleRegisterTxJSON struct { // Type gets the type of this subtype func (m *OracleRegisterTxJSON) Type() string { - return "OracleRegisterTxJSON" + return "OracleRegisterTx" } // SetType sets the type of this subtype diff --git a/generated/models/oracle_response_tx_json.go b/generated/models/oracle_response_tx_json.go index fd7920b1..82cea7f8 100644 --- a/generated/models/oracle_response_tx_json.go +++ b/generated/models/oracle_response_tx_json.go @@ -26,7 +26,7 @@ type OracleResponseTxJSON struct { // Type gets the type of this subtype func (m *OracleResponseTxJSON) Type() string { - return "OracleResponseTxJSON" + return "OracleResponseTx" } // SetType sets the type of this subtype diff --git a/generated/models/spend_tx_json.go b/generated/models/spend_tx_json.go index 293a7ac7..02ff0eb5 100644 --- a/generated/models/spend_tx_json.go +++ b/generated/models/spend_tx_json.go @@ -26,7 +26,7 @@ type SpendTxJSON struct { // Type gets the type of this subtype func (m *SpendTxJSON) Type() string { - return "SpendTxJSON" + return "SpendTx" } // SetType sets the type of this subtype diff --git a/generated_test/error_test.go b/generated_test/error_test.go new file mode 100644 index 00000000..9286120b --- /dev/null +++ b/generated_test/error_test.go @@ -0,0 +1,21 @@ +package generated_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/aeternity/aepp-sdk-go/generated/client/external" + "github.com/aeternity/aepp-sdk-go/generated/models" +) + +func TestErrorModelDereferencing(t *testing.T) { + reason := "A Very Good Reason" + postTransactionBadRequest := external.NewPostTransactionBadRequest() + err := models.Error{Reason: &reason} + postTransactionBadRequest.Payload = &err + printedError := fmt.Sprintf("BadRequest %s", postTransactionBadRequest) + if !strings.Contains(printedError, reason) { + t.Errorf("Expected to find %s when printing out the models.Error: got %s instead", reason, printedError) + } +} diff --git a/generated_test/generic_txs_test.go b/generated_test/generic_txs_test.go new file mode 100644 index 00000000..4c2dfd27 --- /dev/null +++ b/generated_test/generic_txs_test.go @@ -0,0 +1,18 @@ +package generated_test + +import ( + "testing" + + "github.com/aeternity/aepp-sdk-go/generated/models" +) + +// Unfortunately, one must patch the generated go-swagger code to correctly parse the GenericTx into a specific Transaction type. +// Ideally the test JSON snippet should include every possible transaction type, but it only tests SpendTx for now. +func TestGenericTxsPolymorphicDeserialization(t *testing.T) { + genericTxsJSON := []byte("{\"transactions\":[{\"block_hash\":\"mh_qoxFYgZoG7NxocZqBk1Dx9DJZKboT9WCKGFWV26QHKhB3oPDp\",\"block_height\":8835,\"hash\":\"th_uRnWPL3iqiLB7MzsVQ3aAgHsFALd62cmcNkw7ea1n9sfXybcr\",\"signatures\":[\"sg_RBq5vRuRchZ26HCPnZk5Xorn3ooQtfZSxf4mEPCsnpV9UD9KuZvqEKHM3vosoVvCvxvF5CyfdDkzCHnYW8bs3Ai7EtMBY\"],\"tx\":{\"amount\":10,\"fee\":20000000000000,\"nonce\":54,\"payload\":\"Hello World\",\"recipient_id\":\"ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v\",\"sender_id\":\"ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi\",\"ttl\":9335,\"type\":\"SpendTx\",\"version\":1}}]}") + genericTxs := models.GenericTxs{} + err := genericTxs.UnmarshalBinary(genericTxsJSON) + if err != nil { + t.Error(err) + } +} diff --git a/integration_test/integration_test.go b/integration_test/integration_test.go index 9fe54f78..bd181e3c 100644 --- a/integration_test/integration_test.go +++ b/integration_test/integration_test.go @@ -4,15 +4,20 @@ import ( "fmt" "math/big" "os" + "strings" "testing" + "time" "github.com/aeternity/aepp-sdk-go/aeternity" "github.com/aeternity/aepp-sdk-go/utils" ) +var sender = "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi" +var senderPrivateKey = os.Getenv("INTEGRATION_TEST_SENDER_PRIVATE_KEY") +var nodeURL = "http://localhost:3013" +var networkID = "ae_docker" + func TestSpendTxWithNode(t *testing.T) { - sender := "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi" - senderPrivateKey := os.Getenv("INTEGRATION_TEST_SENDER_PRIVATE_KEY") senderAccount, err := aeternity.AccountFromHexString(senderPrivateKey) if err != nil { t.Fatal(err) @@ -20,8 +25,8 @@ func TestSpendTxWithNode(t *testing.T) { recipient := "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v" message := "Hello World" - aeternity.Config.Node.URL = "http://localhost:3013" - aeternity.Config.Node.NetworkID = "ae_docker" + aeternity.Config.Node.URL = nodeURL + aeternity.Config.Node.NetworkID = networkID aeCli := aeternity.NewCli(aeternity.Config.Node.URL, false) // In case this test has been run before, get recipient's account info. If it exists, expectedAmount = amount + 10 @@ -52,11 +57,11 @@ func TestSpendTxWithNode(t *testing.T) { fmt.Println(base64TxMsg) // sign the transaction, output params for debugging - signedBase64TxMsg, _, _, err := aeternity.SignEncodeTxStr(senderAccount, base64TxMsg, aeternity.Config.Node.NetworkID) + signedBase64TxMsg, hash, signature, err := aeternity.SignEncodeTxStr(senderAccount, base64TxMsg, aeternity.Config.Node.NetworkID) if err != nil { t.Error(err) } - fmt.Println(signedBase64TxMsg) + fmt.Println(signedBase64TxMsg, hash, signature) // send the signed transaction to the node err = aeCli.BroadcastTransaction(signedBase64TxMsg) @@ -77,8 +82,6 @@ func TestSpendTxWithNode(t *testing.T) { func TestSpendTxLargeWithNode(t *testing.T) { // This is a separate test because the account may not have enough funds for this test when the node has just started. - sender := "ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi" - senderPrivateKey := os.Getenv("INTEGRATION_TEST_SENDER_PRIVATE_KEY") senderAccount, err := aeternity.AccountFromHexString(senderPrivateKey) if err != nil { t.Fatal(err) @@ -86,7 +89,7 @@ func TestSpendTxLargeWithNode(t *testing.T) { recipient := "ak_Egp9yVdpxmvAfQ7vsXGvpnyfNq71msbdUpkMNYGTeTe8kPL3v" message := "Hello World" - aeternity.Config.Node.URL = "http://localhost:3013" + aeternity.Config.Node.URL = nodeURL aeternity.Config.Node.NetworkID = "ae_docker" aeCli := aeternity.NewCli(aeternity.Config.Node.URL, false) @@ -138,3 +141,209 @@ func TestSpendTxLargeWithNode(t *testing.T) { t.Fatalf("Recipient should have %v, but has %v instead", expectedAmount.String(), recipientAccount.Balance.String()) } } + +func signBroadcast(tx string, acc *aeternity.Account, aeClient *aeternity.Ae) (hash string, err error) { + signedTxStr, hash, _, err := aeternity.SignEncodeTxStr(acc, tx, aeternity.Config.Node.NetworkID) + if err != nil { + fmt.Println(err) + return + } + + err = aeClient.BroadcastTransaction(signedTxStr) + if err != nil { + panic(err) + } + + return hash, nil + +} + +func getHeight(aeClient *aeternity.Ae) (h uint64) { + h, err := aeClient.APIGetHeight() + if err != nil { + fmt.Println("Could not retrieve chain height") + return + } + fmt.Println("Current Height:", h) + return +} + +func waitForTransaction(aeClient *aeternity.Ae, hash string) { + height := getHeight(aeClient) + fmt.Println("Waiting for Transaction...") + height, blockHash, microBlockHash, _, err := aeClient.WaitForTransactionUntilHeight(height+10, hash) + fmt.Println("Transaction was found at", height, "blockhash", blockHash, "microBlockHash", microBlockHash, "err", err) +} + +func getNameEntry(aeClient *aeternity.Ae, name string) (responseJSON string, err error) { + response, err := aeClient.APIGetNameEntryByName(name) + if err != nil { + fmt.Println(err) + return + } + r, _ := response.MarshalBinary() + responseJSON = string(r) + return responseJSON, nil +} + +func TestAENSWorkflow(t *testing.T) { + name := "fdsa.test" + acc, err := aeternity.AccountFromHexString(senderPrivateKey) + if err != nil { + fmt.Println(err) + return + } + aeClient := aeternity.NewCli(nodeURL, false).WithAccount(acc) + aeternity.Config.Node.NetworkID = networkID + aeternity.Config.Client.Fee = *utils.RequireBigIntFromString("100000000000000") + + // Preclaim the name + fmt.Println("PreclaimTx") + preclaimTx, salt, err := aeClient.Aens.NamePreclaimTx(name, aeternity.Config.Client.Fee) + if err != nil { + fmt.Println(err) + return + } + preclaimTxStr, _ := aeternity.BaseEncodeTx(&preclaimTx) + fmt.Println("PreclaimTx and Salt:", preclaimTxStr, salt) + + hash, err := signBroadcast(preclaimTxStr, acc, aeClient) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("Signed & Broadcasted NamePreclaimTx", hash) + + // Wait for a bit + waitForTransaction(aeClient, hash) + + // Claim the name + fmt.Println("NameClaimTx") + claimTx, err := aeClient.Aens.NameClaimTx(name, *salt, aeternity.Config.Client.Fee) + if err != nil { + fmt.Println(err) + return + } + claimTxStr, _ := aeternity.BaseEncodeTx(&claimTx) + fmt.Println("ClaimTx:", claimTxStr) + + hash, err = signBroadcast(claimTxStr, acc, aeClient) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("Signed & Broadcasted NameClaimTx") + + // Wait for a bit + waitForTransaction(aeClient, hash) + + // Verify that the name exists + entryAfterNameClaim, err := getNameEntry(aeClient, name) + fmt.Println(entryAfterNameClaim) + + // Update the name, make it point to something + fmt.Println("NameUpdateTx") + updateTx, err := aeClient.Aens.NameUpdateTx(name, acc.Address) + updateTxStr, _ := aeternity.BaseEncodeTx(&updateTx) + fmt.Println("UpdateTx:", updateTxStr) + + _, err = signBroadcast(updateTxStr, acc, aeClient) + if err != nil { + fmt.Println(err) + return + } + fmt.Println("Signed & Broadcasted NameUpdateTx") + + // Verify that the name was updated + // Sleep a little, it takes time for the entry update to show up + fmt.Printf("Sleeping a bit before querying /names/%s...\n", name) + time.Sleep(1000 * time.Millisecond) + entryAfterNameUpdate, _ := getNameEntry(aeClient, name) + fmt.Println(entryAfterNameUpdate) + + if !strings.Contains(entryAfterNameUpdate, acc.Address) { + t.Errorf("The AENS entry should now point to %s but doesn't: %s", acc.Address, entryAfterNameUpdate) + } + +} + +func TestOracleWorkflow(t *testing.T) { + acc, err := aeternity.AccountFromHexString(senderPrivateKey) + if err != nil { + fmt.Println(err) + return + } + aeternity.Config.Node.NetworkID = networkID + aeClient := aeternity.NewCli(nodeURL, false).WithAccount(acc) + + fmt.Println("OracleRegisterTx") + queryFee := utils.NewBigIntFromUint64(1000) + oracleRegisterTx, err := aeClient.Oracle.OracleRegisterTx("hello", "helloback", *queryFee, 0, 100, 0, 0) + if err != nil { + t.Error(err) + } + oracleRegisterTxStr, _ := aeternity.BaseEncodeTx(&oracleRegisterTx) + oracleRegisterTxHash, err := signBroadcast(oracleRegisterTxStr, acc, aeClient) + if err != nil { + t.Error(err) + } + + waitForTransaction(aeClient, oracleRegisterTxHash) + + // Confirm that the oracle exists + oraclePubKey := strings.Replace(acc.Address, "ak_", "ok_", 1) + oracle, err := aeClient.APIGetOracleByPubkey(oraclePubKey) + if err != nil { + t.Errorf("APIGetOracleByPubkey: %s", err) + } + + fmt.Println("OracleExtendTx") + // save the oracle's initial TTL so we can compare it with after OracleExtendTx + oracleTTL := *oracle.TTL + oracleExtendTx, err := aeClient.Oracle.OracleExtendTx(oraclePubKey, 0, 1000) + if err != nil { + t.Error(err) + } + oracleExtendTxStr, _ := aeternity.BaseEncodeTx(&oracleExtendTx) + oracleExtendTxHash, err := signBroadcast(oracleExtendTxStr, acc, aeClient) + if err != nil { + t.Error(err) + } + waitForTransaction(aeClient, oracleExtendTxHash) + + oracle, err = aeClient.APIGetOracleByPubkey(oraclePubKey) + if err != nil { + t.Errorf("APIGetOracleByPubkey: %s", err) + } + if *oracle.TTL == oracleTTL { + t.Errorf("The Oracle's TTL did not change after OracleExtendTx. Got %v but expected %v", *oracle.TTL, oracleTTL) + } + + fmt.Println("OracleQueryTx") + oracleQueryTx, err := aeClient.Oracle.OracleQueryTx(oraclePubKey, "How was your day?", *queryFee, 0, 100, 0, 100) + if err != nil { + t.Error(err) + } + oracleQueryTxStr, _ := aeternity.BaseEncodeTx(&oracleQueryTx) + oracleQueryTxHash, err := signBroadcast(oracleQueryTxStr, acc, aeClient) + if err != nil { + t.Error(err) + } + waitForTransaction(aeClient, oracleQueryTxHash) + + fmt.Println("OracleRespondTx") + // Find the Oracle Query ID to reply to + oracleQueries, err := aeClient.APIGetOracleQueriesByPubkey(oraclePubKey) + if err != nil { + t.Errorf("APIGetOracleQueriesByPubkey: %s", err) + } + oqID := string(oracleQueries.OracleQueries[0].ID) + oracleRespondTx, err := aeClient.Oracle.OracleRespondTx(oraclePubKey, oqID, "My day was fine thank you", 0, 100) + oracleRespondTxStr, _ := aeternity.BaseEncodeTx(&oracleRespondTx) + oracleRespondTxHash, err := signBroadcast(oracleRespondTxStr, acc, aeClient) + if err != nil { + t.Error(err) + } + waitForTransaction(aeClient, oracleRespondTxHash) + +}