rosetta-lbry/services/construction_service_test.go
2020-09-17 14:40:16 -07:00

399 lines
13 KiB
Go

// Copyright 2020 Coinbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package services
import (
"context"
"encoding/hex"
"testing"
"github.com/coinbase/rosetta-bitcoin/bitcoin"
"github.com/coinbase/rosetta-bitcoin/configuration"
mocks "github.com/coinbase/rosetta-bitcoin/mocks/services"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/stretchr/testify/assert"
)
func forceHexDecode(t *testing.T, s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
t.Fatalf("could not decode hex %s", s)
}
return b
}
func forceMarshalMap(t *testing.T, i interface{}) map[string]interface{} {
m, err := types.MarshalMap(i)
if err != nil {
t.Fatalf("could not marshal map %s", types.PrintStruct(i))
}
return m
}
func TestConstructionService(t *testing.T) {
networkIdentifier = &types.NetworkIdentifier{
Network: bitcoin.TestnetNetwork,
Blockchain: bitcoin.Blockchain,
}
cfg := &configuration.Configuration{
Mode: configuration.Online,
Network: networkIdentifier,
Params: bitcoin.TestnetParams,
Currency: bitcoin.TestnetCurrency,
}
mockIndexer := &mocks.Indexer{}
mockClient := &mocks.Client{}
servicer := NewConstructionAPIService(cfg, mockClient, mockIndexer)
ctx := context.Background()
// Test Derive
publicKey := &types.PublicKey{
Bytes: forceHexDecode(
t,
"0325c9a4252789b31dbb3454ec647e9516e7c596bcde2bd5da71a60fab8644e438",
),
CurveType: types.Secp256k1,
}
deriveResponse, err := servicer.ConstructionDerive(ctx, &types.ConstructionDeriveRequest{
NetworkIdentifier: networkIdentifier,
PublicKey: publicKey,
})
assert.Nil(t, err)
assert.Equal(t, &types.ConstructionDeriveResponse{
AccountIdentifier: &types.AccountIdentifier{
Address: "tb1qcqzmqzkswhfshzd8kedhmtvgnxax48z4fklhvm",
},
}, deriveResponse)
// Test Preprocess
ops := []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: bitcoin.InputOpType,
Account: &types.AccountIdentifier{
Address: "tb1qcqzmqzkswhfshzd8kedhmtvgnxax48z4fklhvm",
},
Amount: &types.Amount{
Value: "-1000000",
Currency: bitcoin.TestnetCurrency,
},
CoinChange: &types.CoinChange{
CoinIdentifier: &types.CoinIdentifier{
Identifier: "b14157a5c50503c8cd202a173613dd27e0027343c3d50cf85852dd020bf59c7f:1",
},
CoinAction: types.CoinSpent,
},
},
{
OperationIdentifier: &types.OperationIdentifier{
Index: 1,
},
Type: bitcoin.OutputOpType,
Account: &types.AccountIdentifier{
Address: "tb1q3r8xjf0c2yazxnq9ey3wayelygfjxpfqjvj5v7",
},
Amount: &types.Amount{
Value: "954843",
Currency: bitcoin.TestnetCurrency,
},
},
{
OperationIdentifier: &types.OperationIdentifier{
Index: 2,
},
Type: bitcoin.OutputOpType,
Account: &types.AccountIdentifier{
Address: "tb1qjsrjvk2ug872pdypp33fjxke62y7awpgefr6ua",
},
Amount: &types.Amount{
Value: "44657",
Currency: bitcoin.TestnetCurrency,
},
},
}
feeMultiplier := float64(0.75)
preprocessResponse, err := servicer.ConstructionPreprocess(
ctx,
&types.ConstructionPreprocessRequest{
NetworkIdentifier: networkIdentifier,
Operations: ops,
SuggestedFeeMultiplier: &feeMultiplier,
},
)
assert.Nil(t, err)
options := &preprocessOptions{
Coins: []*types.Coin{
{
CoinIdentifier: &types.CoinIdentifier{
Identifier: "b14157a5c50503c8cd202a173613dd27e0027343c3d50cf85852dd020bf59c7f:1",
},
Amount: &types.Amount{
Value: "-1000000",
Currency: bitcoin.TestnetCurrency,
},
},
},
EstimatedSize: 142,
FeeMultiplier: &feeMultiplier,
}
assert.Equal(t, &types.ConstructionPreprocessResponse{
Options: forceMarshalMap(t, options),
}, preprocessResponse)
// Test Metadata
metadata := &constructionMetadata{
ScriptPubKeys: []*bitcoin.ScriptPubKey{
{
ASM: "0 c005b00ad075d30b89a7b65b7dad8899ba6a9c55",
Hex: "0014c005b00ad075d30b89a7b65b7dad8899ba6a9c55",
RequiredSigs: 1,
Type: "witness_v0_keyhash",
Addresses: []string{
"tb1qcqzmqzkswhfshzd8kedhmtvgnxax48z4fklhvm",
},
},
},
}
// Normal Fee
mockIndexer.On(
"GetScriptPubKeys",
ctx,
options.Coins,
).Return(
metadata.ScriptPubKeys,
nil,
).Once()
mockClient.On(
"SuggestedFeeRate",
ctx,
defaultConfirmationTarget,
).Return(
bitcoin.MinFeeRate*10,
nil,
).Once()
metadataResponse, err := servicer.ConstructionMetadata(ctx, &types.ConstructionMetadataRequest{
NetworkIdentifier: networkIdentifier,
Options: forceMarshalMap(t, options),
})
assert.Nil(t, err)
assert.Equal(t, &types.ConstructionMetadataResponse{
Metadata: forceMarshalMap(t, metadata),
SuggestedFee: []*types.Amount{
{
Value: "1065", // 1,420 * 0.75
Currency: bitcoin.TestnetCurrency,
},
},
}, metadataResponse)
// Low Fee
mockIndexer.On(
"GetScriptPubKeys",
ctx,
options.Coins,
).Return(
metadata.ScriptPubKeys,
nil,
).Once()
mockClient.On(
"SuggestedFeeRate",
ctx,
defaultConfirmationTarget,
).Return(
bitcoin.MinFeeRate,
nil,
).Once()
metadataResponse, err = servicer.ConstructionMetadata(ctx, &types.ConstructionMetadataRequest{
NetworkIdentifier: networkIdentifier,
Options: forceMarshalMap(t, options),
})
assert.Nil(t, err)
assert.Equal(t, &types.ConstructionMetadataResponse{
Metadata: forceMarshalMap(t, metadata),
SuggestedFee: []*types.Amount{
{
Value: "142", // we don't go below minimum fee rate
Currency: bitcoin.TestnetCurrency,
},
},
}, metadataResponse)
// Test Payloads
unsignedRaw := "7b227472616e73616374696f6e223a2230313030303030303031376639636635306230326464353235386638306364356333343337333032653032376464313333363137326132306364633830333035633561353537343162313031303030303030303066666666666666663032646239313065303030303030303030303136303031343838636536393235663835313361323334633035633932326565393333663232313332333035323037316165303030303030303030303030313630303134393430373236353935633431666361306234383130633632393931616439643238396565623832383030303030303030222c227363726970745075624b657973223a5b7b2261736d223a22302063303035623030616430373564333062383961376236356237646164383839396261366139633535222c22686578223a223030313463303035623030616430373564333062383961376236356237646164383839396261366139633535222c2272657153696773223a312c2274797065223a227769746e6573735f76305f6b657968617368222c22616464726573736573223a5b227462317163717a6d717a6b7377686673687a64386b6564686d7476676e78617834387a34666b6c68766d225d7d5d2c22696e7075745f616d6f756e7473223a5b222d31303030303030225d2c22696e7075745f616464726573736573223a5b227462317163717a6d717a6b7377686673687a64386b6564686d7476676e78617834387a34666b6c68766d225d7d" // nolint
payloadsResponse, err := servicer.ConstructionPayloads(ctx, &types.ConstructionPayloadsRequest{
NetworkIdentifier: networkIdentifier,
Operations: ops,
Metadata: forceMarshalMap(t, metadata),
})
val0 := int64(0)
val1 := int64(1)
parseOps := []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
NetworkIndex: &val0,
},
Type: bitcoin.InputOpType,
Account: &types.AccountIdentifier{
Address: "tb1qcqzmqzkswhfshzd8kedhmtvgnxax48z4fklhvm",
},
Amount: &types.Amount{
Value: "-1000000",
Currency: bitcoin.TestnetCurrency,
},
CoinChange: &types.CoinChange{
CoinIdentifier: &types.CoinIdentifier{
Identifier: "b14157a5c50503c8cd202a173613dd27e0027343c3d50cf85852dd020bf59c7f:1",
},
CoinAction: types.CoinSpent,
},
},
{
OperationIdentifier: &types.OperationIdentifier{
Index: 1,
NetworkIndex: &val0,
},
Type: bitcoin.OutputOpType,
Account: &types.AccountIdentifier{
Address: "tb1q3r8xjf0c2yazxnq9ey3wayelygfjxpfqjvj5v7",
},
Amount: &types.Amount{
Value: "954843",
Currency: bitcoin.TestnetCurrency,
},
},
{
OperationIdentifier: &types.OperationIdentifier{
Index: 2,
NetworkIndex: &val1,
},
Type: bitcoin.OutputOpType,
Account: &types.AccountIdentifier{
Address: "tb1qjsrjvk2ug872pdypp33fjxke62y7awpgefr6ua",
},
Amount: &types.Amount{
Value: "44657",
Currency: bitcoin.TestnetCurrency,
},
},
}
assert.Nil(t, err)
signingPayload := &types.SigningPayload{
Bytes: forceHexDecode(
t,
"7b98f8b77fa6ef34044f320073118033afdffbd3fd3f8423889d9e5953ff4a30",
),
AccountIdentifier: &types.AccountIdentifier{
Address: "tb1qcqzmqzkswhfshzd8kedhmtvgnxax48z4fklhvm",
},
SignatureType: types.Ecdsa,
}
assert.Equal(t, &types.ConstructionPayloadsResponse{
UnsignedTransaction: unsignedRaw,
Payloads: []*types.SigningPayload{signingPayload},
}, payloadsResponse)
// Test Parse Unsigned
parseUnsignedResponse, err := servicer.ConstructionParse(ctx, &types.ConstructionParseRequest{
NetworkIdentifier: networkIdentifier,
Signed: false,
Transaction: unsignedRaw,
})
assert.Nil(t, err)
assert.Equal(t, &types.ConstructionParseResponse{
Operations: parseOps,
AccountIdentifierSigners: []*types.AccountIdentifier{},
}, parseUnsignedResponse)
// Test Combine
signedRaw := "7b227472616e73616374696f6e223a22303130303030303030303031303137663963663530623032646435323538663830636435633334333733303265303237646431333336313732613230636463383033303563356135353734316231303130303030303030306666666666666666303264623931306530303030303030303030313630303134383863653639323566383531336132333463303563393232656539333366323231333233303532303731616530303030303030303030303031363030313439343037323635393563343166636130623438313063363239393161643964323839656562383238303234373330343430323230323538373665633862396635316433343361356135366163353439633063383238303035656634356562653964613136366462363435633039313537323233663032323034636430386237323738613838383961383131333539313562636531306431656633626239326232313766383161306465376537396666623364666436616335303132313033323563396134323532373839623331646262333435346563363437653935313665376335393662636465326264356461373161363066616238363434653433383030303030303030222c22696e7075745f616d6f756e7473223a5b222d31303030303030225d7d" // nolint
combineResponse, err := servicer.ConstructionCombine(ctx, &types.ConstructionCombineRequest{
NetworkIdentifier: networkIdentifier,
UnsignedTransaction: unsignedRaw,
Signatures: []*types.Signature{
{
Bytes: forceHexDecode(
t,
"25876ec8b9f51d343a5a56ac549c0c828005ef45ebe9da166db645c09157223f4cd08b7278a8889a81135915bce10d1ef3bb92b217f81a0de7e79ffb3dfd6ac5", // nolint
),
SigningPayload: signingPayload,
PublicKey: publicKey,
SignatureType: types.Ecdsa,
},
},
})
assert.Nil(t, err)
assert.Equal(t, &types.ConstructionCombineResponse{
SignedTransaction: signedRaw,
}, combineResponse)
// Test Parse Signed
parseSignedResponse, err := servicer.ConstructionParse(ctx, &types.ConstructionParseRequest{
NetworkIdentifier: networkIdentifier,
Signed: true,
Transaction: signedRaw,
})
assert.Nil(t, err)
assert.Equal(t, &types.ConstructionParseResponse{
Operations: parseOps,
AccountIdentifierSigners: []*types.AccountIdentifier{
{Address: "tb1qcqzmqzkswhfshzd8kedhmtvgnxax48z4fklhvm"},
},
}, parseSignedResponse)
// Test Hash
transactionIdentifier := &types.TransactionIdentifier{
Hash: "6d87ad0e26025128f5a8357fa423b340cbcffb9703f79f432f5520fca59cd20b",
}
hashResponse, err := servicer.ConstructionHash(ctx, &types.ConstructionHashRequest{
NetworkIdentifier: networkIdentifier,
SignedTransaction: signedRaw,
})
assert.Nil(t, err)
assert.Equal(t, &types.TransactionIdentifierResponse{
TransactionIdentifier: transactionIdentifier,
}, hashResponse)
// Test Submit
bitcoinTransaction := "010000000001017f9cf50b02dd5258f80cd5c3437302e027dd1336172a20cdc80305c5a55741b10100000000ffffffff02db910e000000000016001488ce6925f8513a234c05c922ee933f221323052071ae000000000000160014940726595c41fca0b4810c62991ad9d289eeb82802473044022025876ec8b9f51d343a5a56ac549c0c828005ef45ebe9da166db645c09157223f02204cd08b7278a8889a81135915bce10d1ef3bb92b217f81a0de7e79ffb3dfd6ac501210325c9a4252789b31dbb3454ec647e9516e7c596bcde2bd5da71a60fab8644e43800000000" // nolint
mockClient.On(
"SendRawTransaction",
ctx,
bitcoinTransaction,
).Return(
transactionIdentifier.Hash,
nil,
)
submitResponse, err := servicer.ConstructionSubmit(ctx, &types.ConstructionSubmitRequest{
NetworkIdentifier: networkIdentifier,
SignedTransaction: signedRaw,
})
assert.Nil(t, err)
assert.Equal(t, &types.TransactionIdentifierResponse{
TransactionIdentifier: transactionIdentifier,
}, submitResponse)
mockClient.AssertExpectations(t)
mockIndexer.AssertExpectations(t)
}